Skip to content

feat(cli): note CRUD/read commands with exit codes and i18n#18

Merged
goofmint merged 2 commits into
mainfrom
feature/cli-crud-commands
Jun 16, 2026
Merged

feat(cli): note CRUD/read commands with exit codes and i18n#18
goofmint merged 2 commits into
mainfrom
feature/cli-crud-commands

Conversation

@goofmint

@goofmint goofmint commented Jun 16, 2026

Copy link
Copy Markdown
Owner

概要

ユーザー向けの dennoh CLI コマンド群を、コア関数の薄いラッパーとして実装しました(T11 系 + T11.14/T11.15、T12.2)。

実装コマンド

すべて (args: string[], io: CliIO) => Promise<number> シグネチャ。

コマンド 機能
add "<text>" メモ作成(stdin パイプ対応)
update <id> "<text>" 内容置換(stdin パイプ対応)
delete <id> メモ削除
get <id> [--json] 1件表示(人間可読 / JSON)
search "<q>" [--project X] [--tag Y] [--limit N] [--json] FTS 検索
recent [--limit N] [--json] 最近更新の一覧
reindex インデックス再構築

共通ヘルパー: src/cli/flags.ts--flag / --opt value / --opt=value 解析)、src/cli/stdin.ts

規約の適用

  • 終了コード (src/cli/types.ts): EXIT_SUCCESS=0 / EXIT_USER_ERROR=1(引数不足・ID不存在・バリデーション・不明コマンド)/ EXIT_INTERNAL_ERROR=2(DB接続失敗・予期しない例外)。update/deletegetNoteById で事前存在チェックし、ID不存在を確実に 1 に分類。
  • i18n: status.ts パターンに倣い各コマンド内 MESSAGES: Record<Lang, {...}> + resolveLang() で日英対応。main.ts の usage / 不明コマンドもバイリンガル化。

注意点・設計判断

  • stdin パイプ判定は process.stdin.isTTY !== true。実パイプでは isTTYundefined になるため、仕様文言の === false ではパイプ入力を拾えない(意図に合わせて修正)。
  • i18n は集中辞書方式ではない。要件 T12.1(src/i18n/ja.ts/en.ts フラットキー)は未導入で、コマンド単位の MESSAGES 定数で実装。history/restore/serve/config は英語のまま(未 localize)。→ T12.1/T12.3/T12.4 は別タスクとして .tmp/tasks.md に残置。
  • search のデフォルト出力は1行1件。スニペットに含まれる改行を空白へ正規化して1行を維持(<mark> タグは無加工)。

テスト

各コマンドのユニットテストを追加(happy path / --json / not-found / usage / 終了コード2 / DENNOH_LANG=en ローカライズ)。

  • bun test: 433 pass / 0 fail
  • bun run lint / bun run typecheck: クリーン

🤖 Generated with Claude Code

Summary by CodeRabbit

リリースノート

新機能

  • CLI に add / update / delete / get / search / recent / reindex を追加
  • get / recent / search は JSON 形式でも出力可能(プロジェクト・タグで検索/絞り込み)
  • パイプ/リダイレクト入力に対応

改善

  • ヘルプ・エラーメッセージを 日本語/英語(ja/en)で統一し、終了コードも整理

テスト

  • 主要 CLI コマンドの統合テストを追加

ドキュメント

  • 進捗管理ドキュメントを更新

Add the user-facing dennoh CLI commands as thin wrappers over the core:

- Data ops: add, update, delete (stdin-piped content supported)
- Read ops: get, search, recent, reindex (with --json output)
- Shared flag/stdin helpers (src/cli/flags.ts, src/cli/stdin.ts)

Conventions applied across all new commands and the main dispatcher:

- Exit codes (src/cli/types.ts): EXIT_SUCCESS=0, EXIT_USER_ERROR=1
  (bad args / unknown id / validation / unknown command),
  EXIT_INTERNAL_ERROR=2 (DB open failure / unexpected exception).
- Bilingual (ja/en) messages via per-command MESSAGES + resolveLang(),
  following the status.ts pattern; main usage()/unknown-command localized.

stdin piping gates on `process.stdin.isTTY !== true` (a real pipe leaves
isTTY undefined, so `=== false` would never trigger the stdin read path).

Per-command unit tests cover happy paths, --json, not-found, usage errors,
the internal-error (code 2) path, and English localization via DENNOH_LANG.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jun 16, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

ウォークスルー

フラグパーサー・終了コード定数・stdin ヘルパーを共有インフラとして追加し、add/update/delete/get/search/recent/reindex の7サブコマンドを実装。main.ts のルーターを多言語対応で拡張し、各コマンドの統合テストを追加。タスク進捗ドキュメントを更新。

変更点

CLI コマンド群の実装と統合テスト追加

Layer / File(s) Summary
CLI 基盤インフラ
src/cli/types.ts, src/cli/flags.ts, src/cli/stdin.ts
終了コード定数(EXIT_SUCCESS=0、EXIT_USER_ERROR=1、EXIT_INTERNAL_ERROR=2)を追加。takeBooleanFlag(複数フラグ除去)と takeOption(--name value/--name=value 両形式対応・値欠落検出)のフラグパーサー実装。readStdin() 非同期ヘルパーで process.stdin をバッファリング読み取り。
add コマンド(テキスト保存)
src/cli/commands/add.ts
引数または非 TTY stdin からテキスト取得。readConfig/openDatabase でvaultPath・DB 準備。runMigrations 後に saveMemory で保存・ID 出力。ContentValidationError をユーザーエラーにマッピング。finally で DB クローズ保証。
update コマンド(テキスト置換)
src/cli/commands/update.ts
id 必須。テキスト第2引数優先、未指定+非 TTY stdin のみ readStdin() で取得。getNoteById で事前存在確認後 updateMemory 実行。isNotFoundError・ContentValidationError を適切に分類。finally で DB クローズ。
delete コマンド(ソフトデリート)
src/cli/commands/delete.ts
id 必須。getNoteById で存在確認し未存在ならユーザーエラー。deleteMemory で削除実行。isNotFoundError をユーザーエラーにマッピング。finally で DB クローズ。
get コマンド(メモ取得)
src/cli/commands/get.ts
id 必須。--json フラグ解析。getNote で取得・未存在ならユーザーエラー。JSON または人間可読形式(id/projects/tags/body)で出力。finally で DB クローズ。
search コマンド(検索・フィルター)
src/cli/commands/search.ts
クエリ必須。--json/--project/--tag/--limit を解析し SearchFilters 構築。searchMemory 実行。JSON または行ごと(id/label/updatedAt)出力。finally で DB クローズ。
recent コマンド(最近順表示)
src/cli/commands/recent.ts
--json/--limit(正の整数)を解析。余分な引数はユーザーエラー。listRecent 実行。JSON 時に projects_json/tags_json を配列に変換。行ごと(id/title or path/updated_at)出力。finally で DB クローズ。
reindex コマンド(再インデックス)
src/cli/commands/reindex.ts
余分な引数はユーザーエラー。reindexAll 実行で処理件数・errors・translationErrors を stdout 出力。finally で DB クローズ。
main.ts ルーター拡張・多言語化
src/cli/main.ts, src/cli/index.ts
resolveLang() で ja/en 判定。USAGE・UNKNOWN_COMMAND を言語別に定義。add/update/delete/get/search/recent/reindex を await command(rest, io) で明示的にルーティング。不明コマンド時は言語別メッセージ+usage 出力・EXIT_USER_ERROR。バレルエクスポートに終了コード定数と全コマンドを追加。
add/update 統合テスト
tests/cli/add.test.ts, tests/cli/update.test.ts
withStdin/makeIO ヘルパーで入出力をバッファリング。引数・stdin パイプ・TTY ガード・マイグレーション自動実行・DB 失敗・未知 ID・ID 欠落の各ケースを検証。
delete/get 統合テスト
tests/cli/delete.test.ts, tests/cli/get.test.ts
正常削除・二重削除・ファイル消失確認・人間可読/JSON 出力・DENNOH_LANG=en による英語化の各ケース検証。
search/recent/reindex 統合テスト
tests/cli/search.test.ts, tests/cli/recent.test.ts, tests/cli/reindex.test.ts
--project フィルター・--json 出力・projects/tags 配列変換・不正 --limit・クエリ欠落・再インデックス復元・想定外引数エラーを検証。
タスク進捗ドキュメント更新
.tmp/tasks.md
T10.11 完了済みに更新・検証手順追記。T11 CLI タスク全7コマンドを完了済みに切り替え。add/update の stdin パイプ対応補足を追加。T12.i18n 現状メモ追記・未完了タスク維持。

シーケンス図

sequenceDiagram
  participant User as ユーザー
  participant main as main.ts
  participant cmd as コマンド関数
  participant flags as フラグ解析
  participant config as readConfig
  participant db as DB操作
  participant mem as メモ操作

  User->>main: dennoh subcommand args
  main->>main: resolveLang()
  main->>cmd: await command(rest, io)
  cmd->>flags: takeBooleanFlag/takeOption
  cmd->>config: readConfig()
  cmd->>db: openDatabase/runMigrations
  cmd->>mem: saveMemory/updateMemory
  mem-->>cmd: 結果
  cmd-->>main: 終了コード
  cmd->>db: closeDatabase(finally)
Loading

推定コードレビュー工数

🎯 4 (Complex) | ⏱️ ~60 minutes

関連する可能性のある Issue

  • T11の実装 #17: このPRは Issue #17 で計画された T11 CLI コマンド群(add/delete/get/search/reindex/recent/update)とそのインフラ(フラグパーサー・stdin ヘルパー・終了コード定数)を全て実装しており、Issue の目標を直接達成している。

🐰 七つのコマンド、パイプに乗って

stdin をパイプで流して、
dennoh add とひとこえ鳴いた。
--json でのぞけば
メモがずらりと並んでいる。
検索・最近・削除・更新、
七つのコマンド、全部できた! 🎉

🚥 Pre-merge checks | ✅ 5 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 60.87% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
インポート順 ⚠️ Warning src/cli/main.ts のインポート順が Biome 正準順に従っていません。相対インポート(@/config)が外部パッケージ(../../package.json)より先に来ています。 src/cli/main.ts の 3 行目と 4 行目を入れ替えて、外部パッケージのインポートを相対インポートより前に配置してください。
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed PRのタイトルは主要な変更内容を正確に要約し、十分な具体性と明確性があります。
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
コメント ✅ Passed All newly added CLI command files (add, update, delete, get, search, recent, reindex) and utility files (flags, stdin, types) contain appropriate English comments explaining purpose, behavior, desi...

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/cli-crud-commands
✨ Simplify code
  • Create PR with simplified code
  • Commit simplified code in branch feature/cli-crud-commands
  • 🛠️ コメント記述
  • 🛠️ Import

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 11

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/cli/commands/delete.ts`:
- Around line 60-73: The code checks for note existence before deletion, but a
race condition can occur where the note is deleted between the check and the
deleteMemory call, causing deleteMemory to throw a not-found error that gets
caught as a generic internal error. Add a check in the catch block (where error
e is caught) to detect if the error from deleteMemory is a not-found error, and
if so, respond with io.stderr using an appropriate not-found message and return
EXIT_USER_ERROR instead of EXIT_INTERNAL_ERROR, similar to how the pre-check
handles missing notes at the getNoteById verification.

In `@src/cli/commands/recent.ts`:
- Around line 39-54: The `--limit` option validation does not detect when the
flag is provided without a value. Currently when a user runs `dennoh recent
--limit` without a value, the takeOption function returns undefined for
limitArg, which is then treated as if the option was not provided at all,
allowing the code to use DEFAULT_LIMIT instead of failing. You need to
distinguish between two cases: when `--limit` is not provided at all versus when
`--limit` is explicitly provided but lacks a value. Check the return value from
takeOption to determine if the option was explicitly present with no value, and
if so, call io.stderr with an appropriate error message and return
EXIT_USER_ERROR before the code reaches the DEFAULT_LIMIT assignment.

In `@src/cli/commands/search.ts`:
- Around line 37-58: The code does not validate when option tokens like --limit,
--project, or --tag are provided without values. When takeOption consumes a
token but returns undefined for the value, the code currently continues with
defaults or undefined values instead of reporting an error. Add validation after
each takeOption call for --project, --tag, and --limit to check if the option
token was consumed (args array decreased in length) but the value is undefined,
and return EXIT_USER_ERROR with an appropriate error message in those cases.
This ensures users cannot accidentally omit option values (e.g., `search foo
--limit` without a number) without being notified.

In `@src/cli/commands/update.ts`:
- Around line 79-93: There is a TOCTOU race condition between the existence
check using getNoteById and the updateMemory call: if the note is deleted
concurrently between these two operations, updateMemory will throw an exception
that currently falls through to the catch block and returns EXIT_INTERNAL_ERROR
instead of EXIT_USER_ERROR. Fix this by explicitly checking if the exception
caught in the catch block is a "note not found" type error (either by checking
for a dedicated error type thrown by updateMemory or by examining the error
message), and if so, return EXIT_USER_ERROR instead of EXIT_INTERNAL_ERROR to
match the user error contract established at line 82-84 where a missing note is
treated as a user error.

In `@src/cli/flags.ts`:
- Around line 44-50: The flag parsing logic in the block starting with `if (arg
=== name)` unconditionally treats the next token as a value if it exists, which
causes subsequent flags (starting with `-`) to be incorrectly consumed as
values. Add an additional condition to check that the next token does not start
with a hyphen before assigning it as the value. Specifically, when checking `if
(next !== undefined)`, also verify that `next` does not begin with `-` to ensure
that flags are not misinterpreted as values, allowing proper detection of
missing required values and preventing unintended flag consumption.

In `@src/cli/stdin.ts`:
- Around line 4-7: The comment in the stdin.ts file (lines 4-7) describes the
TTY check as process.stdin.isTTY === false, but the actual implementation
approach uses process.stdin.isTTY !== true to handle cases where stdin is
undefined when piped. Update the comment to correctly reference the !== true
pattern instead of === false to ensure future callers understand the correct
guard condition and avoid implementing incorrect TTY checks.

In `@tests/cli/add.test.ts`:
- Around line 1-4: The `DENNOH_TRANSLATE_DISABLE` environment variable is being
set at the module top level in both test files, causing persistent side effects
that break test isolation. In tests/cli/add.test.ts (lines 1-4): remove the
top-level assignment of `process.env.DENNOH_TRANSLATE_DISABLE = "1"` and instead
add a `beforeEach` hook to save the original value and set it to "1", then add
an `afterEach` hook to restore the original value. In tests/cli/update.test.ts
(lines 1-4): apply the same fix by removing the top-level assignment and using
`beforeEach` and `afterEach` hooks to properly scope the environment variable
changes so they do not leak between tests.

In `@tests/cli/get.test.ts`:
- Around line 119-123: The if-else statement in the finally block (lines
119-123) violates the testing guideline that prohibits if statements for control
flow branching in test files. The condition checking whether prev is undefined
to either delete or restore the DENNOH_LANG environment variable should be
refactored to eliminate the branching statement. Replace this if-else structure
with a ternary operator or logical operator approach that accomplishes the same
restoration logic without using an if statement, ensuring the code conforms to
the no-branching style requirement for test files.

In `@tests/cli/reindex.test.ts`:
- Around line 73-77: The test for the reindex command summary in the Japanese
language configuration is incomplete and does not verify the translation errors
count, which is part of the command's public output contract. Add an additional
expect statement that explicitly verifies the translation errors message (翻訳エラー
0 件) is present in the stdout output, similar to the existing checks for the
reindex message and error count. This ensures regressions in the
translationErrors output are caught by the test.

In `@tests/cli/search.test.ts`:
- Around line 1-4: The environment variable DENNOH_TRANSLATE_DISABLE is being
set at module scope, which causes it to persist across test suites and creates
test order dependencies. Move this environment variable assignment into a
beforeEach hook where you set it, and add a corresponding afterEach hook that
restores the original value (or deletes it if it didn't exist before). This
ensures proper test isolation and prevents side effects from affecting other
test suites running in the same process.
- Around line 61-71: The test "prints one line per hit in default form" has weak
assertions that only check if the ID is contained in stdout, failing to validate
the core behavior stated in the test name. Add explicit assertions to verify
that stdout contains exactly one line of output and that the line is properly
formatted with the expected content for the single hit. Use string manipulation
or regex to count and validate the output lines, ensuring the test properly
catches regressions where the formatting or line count changes.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: e0a81205-65b4-4e95-8b55-1a449cb86710

📥 Commits

Reviewing files that changed from the base of the PR and between 746d7a9 and 88e1b93.

📒 Files selected for processing (20)
  • .tmp/tasks.md
  • src/cli/commands/add.ts
  • src/cli/commands/delete.ts
  • src/cli/commands/get.ts
  • src/cli/commands/recent.ts
  • src/cli/commands/reindex.ts
  • src/cli/commands/search.ts
  • src/cli/commands/update.ts
  • src/cli/flags.ts
  • src/cli/index.ts
  • src/cli/main.ts
  • src/cli/stdin.ts
  • src/cli/types.ts
  • tests/cli/add.test.ts
  • tests/cli/delete.test.ts
  • tests/cli/get.test.ts
  • tests/cli/recent.test.ts
  • tests/cli/reindex.test.ts
  • tests/cli/search.test.ts
  • tests/cli/update.test.ts

Comment thread src/cli/commands/delete.ts
Comment thread src/cli/commands/recent.ts Outdated
Comment thread src/cli/commands/search.ts Outdated
Comment thread src/cli/commands/update.ts
Comment thread src/cli/flags.ts
Comment thread tests/cli/add.test.ts Outdated
Comment thread tests/cli/get.test.ts Outdated
Comment thread tests/cli/reindex.test.ts
Comment thread tests/cli/search.test.ts Outdated
Comment thread tests/cli/search.test.ts
AI Agents prompts に基づく自動修正

- flags.ts: takeOption が `-` 始まりの次トークンを値として誤消費しないように
  し、`present` を返してオプション値欠落を呼び出し側が検出可能に
- search/recent: オプション値欠落(例 `search foo --limit`)を EXIT_USER_ERROR
  で明示的に拒否
- update/delete: 事前存在チェック後の TOCTOU race による not-found を catch 側で
  EXIT_USER_ERROR にマップ(isNotFoundError を types.ts に追加)
- stdin.ts: TTY 判定コメントを `!== true` ベースに修正
- tests: DENNOH_TRANSLATE_DISABLE をモジュール先頭代入から beforeEach/afterEach
  によるスコープ化+復元へ(add/update/search)。finally 内の if を三項+Reflect へ。
  reindex の翻訳エラー件数アサーション追加、search の「1ヒット=1行」検証強化、
  オプション値欠落のテスト追加

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (1)
tests/cli/recent.test.ts (1)

1-3: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

環境変数のモジュールスコープ固定をやめ、各テストで復元してください。

Line 1-3 で process.env.DENNOH_TRANSLATE_DISABLE をモジュールスコープで設定しており、同一プロセス内の他スイートへ副作用が漏れます。共有原因は「グローバル状態をセットしたまま復元していないこと」です。

  • tests/cli/recent.test.ts#L1-L3: beforeEach で元値を退避して設定し、afterEach で元値復元(未定義なら削除)してください。
  • tests/cli/reindex.test.ts#L1-L3: 同じく beforeEach/afterEach でスコープ化し、環境変数のリークを防いでください。

As per coding guidelines, tests/**/*.ts では「テストの独立性: テスト間に依存がないか。命名規約が一貫しているか。」を重点確認します。

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/cli/recent.test.ts` around lines 1 - 3, The environment variable
DENNOH_TRANSLATE_DISABLE is being set at module scope, causing side effects to
leak to other test suites in the same process. For both tests/cli/recent.test.ts
(lines 1-3, anchor) and tests/cli/reindex.test.ts (lines 1-3, sibling), move the
module-level process.env.DENNOH_TRANSLATE_DISABLE assignment into a beforeEach
hook where you first save the original value of the environment variable, then
set it to "1". In the corresponding afterEach hook, restore the original value
or delete the property if it was undefined. This ensures test isolation and
prevents environment variable leaks between test suites.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Duplicate comments:
In `@tests/cli/recent.test.ts`:
- Around line 1-3: The environment variable DENNOH_TRANSLATE_DISABLE is being
set at module scope, causing side effects to leak to other test suites in the
same process. For both tests/cli/recent.test.ts (lines 1-3, anchor) and
tests/cli/reindex.test.ts (lines 1-3, sibling), move the module-level
process.env.DENNOH_TRANSLATE_DISABLE assignment into a beforeEach hook where you
first save the original value of the environment variable, then set it to "1".
In the corresponding afterEach hook, restore the original value or delete the
property if it was undefined. This ensures test isolation and prevents
environment variable leaks between test suites.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: d594c10e-7297-4a3e-9495-cd085b7e1239

📥 Commits

Reviewing files that changed from the base of the PR and between 88e1b93 and 1906364.

📒 Files selected for processing (13)
  • src/cli/commands/delete.ts
  • src/cli/commands/recent.ts
  • src/cli/commands/search.ts
  • src/cli/commands/update.ts
  • src/cli/flags.ts
  • src/cli/stdin.ts
  • src/cli/types.ts
  • tests/cli/add.test.ts
  • tests/cli/get.test.ts
  • tests/cli/recent.test.ts
  • tests/cli/reindex.test.ts
  • tests/cli/search.test.ts
  • tests/cli/update.test.ts

@goofmint goofmint merged commit 675fbf1 into main Jun 16, 2026
1 check passed
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