ask-later 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,9 @@
1
+ {
2
+ "name": "ask-later",
3
+ "version": "0.1.0",
4
+ "description": "Ask now, get the answer later. Shelves the agent's questions in PENDING.md, injects the human's answers from INBOX.md on the next tool call, and blocks the stop (twice by default) while unread lines remain. For Claude Code; unofficial.",
5
+ "author": { "name": "metamol0627" },
6
+ "license": "MIT",
7
+ "keywords": ["hooks", "unattended", "human-in-the-loop", "inbox", "pending", "async"],
8
+ "hooks": "./hooks/hooks.json"
9
+ }
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 metamol0627
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.en.md ADDED
@@ -0,0 +1,125 @@
1
+ # ask-later — ask now, get the answer later (an async question queue for Claude Code)
2
+
3
+ Two files and five hooks that keep Claude Code from stopping at "may I?".
4
+ The agent files what it needs from a human in `PENDING.md`, shelves only the work that depends on the answer, and keeps going.
5
+ The human writes one line in `INBOX.md` with any text editor. A hook injects that line into the agent's context on its next tool call,
6
+ and blocks the stop while unread lines remain (twice by default). A sentinel file named `!pending_3.txt` at the project root means "3 open items".
7
+
8
+ Node.js 20+, zero dependencies, MIT, unofficial. **Claude Code only** — it speaks Claude Code's hook contract (JSON on stdin, `hookSpecificOutput.additionalContext`, `decision: block`) and has no path for other agents.
9
+ Events used: `SessionStart` / `UserPromptSubmit` / `PostToolUse` / `Stop` / `SessionEnd`.
10
+ Verified only on Windows 11, Node 24, Claude Code 2.1.268 (macOS/Linux not yet tried). The Japanese `README.md` is the primary document; this is a summary.
11
+
12
+ ## Read this first (what gets written)
13
+
14
+ - `npx ask-later init` writes three things: **the `hooks` key of `.claude/settings.json`** (other keys and other people's hooks are left alone),
15
+ **`CLAUDE.md`** (appends the `## Async queue (ask-later)` rule if it is not there; `--no-claude-md` to skip) and **`.gitignore`** (two lines, only if missing; `--no-gitignore` to skip).
16
+ It also creates `.claude/ask-later/` (a copy of the hook code), `PENDING.md` / `INBOX.md` (kept if they exist) and `.ask-later/config.json`.
17
+ **`npx ask-later uninit` restores `settings.json` to the same content**, keeping the original indent width, line endings and trailing newline (tests cover 2-space, 4-space, tab, CRLF and no-trailing-newline files;
18
+ a hand-written file with arrays folded on one line gets the same content but expanded arrays). Lines added to `CLAUDE.md` / `.gitignore` are left in place.
19
+ - **Hooks always exit 0 and never stop the agent.** Missing or broken files, non-JSON stdin, unwritable disk — all pass silently, leaving one readable line in `.ask-later/hook.log`.
20
+ - The stop block (`Stop` hook, `decision: block`) fires **up to `stopBlockMax` times per unread set and session** (default 2, max 7; Claude Code itself gives up after 8 consecutive blocks, so there is no infinite loop). After that, if the agent still ignores the injection, the session ends with lines unread — the sentinel file and `npx ask-later status` still show them.
21
+ - OS notifications (Windows toast / macOS `osascript` / Linux `notify-send`) are best-effort. The silent sentinel file is the primary signal.
22
+ - Depends on Claude Code's hook JSON contract (`additionalContext`, `decision: block`). If that changes, this breaks.
23
+ - Unofficial. Claude and Claude Code are trademarks of Anthropic, PBC; the author is not affiliated with Anthropic.
24
+
25
+ ## Setup (top to bottom)
26
+
27
+ ```
28
+ npx ask-later init --lang en # hooks + templates + CLAUDE.md rule + .gitignore lines
29
+ npx ask-later status # PENDING open: 0 / INBOX unread: 0 / sentinel: none (0 open) / hook files: 0.1.0
30
+ ```
31
+
32
+ `init` appends the rule the agent follows to `CLAUDE.md` (`npx ask-later snippet --lang en` prints it). Without that rule, injections change nothing.
33
+ It also adds `.ask-later/` and `\!pending_*.txt` to `.gitignore` (the backslash matters — a leading `!` would negate the pattern).
34
+
35
+ **30-second check**: start `claude`, write one line below the `---` in `INBOX.md` (e.g. `test: reply "read" if you can see this`), send any prompt. **The check is that the line gets a leading `✔`** (the reply usually contains it too, but a model may decline to echo text it suspects is an embedded instruction — the `✔` means it arrived). If not, look at `.ask-later/hook.log`.
36
+
37
+ When the agent needs you, it writes an item like this under `# Open` in `PENDING.md`:
38
+
39
+ ```
40
+ ## [P-001] Put the npm token in .env
41
+
42
+ - [ ] status: open
43
+ - filed: 2026-09-16
44
+ - context: preparing the publish; only you can issue the token
45
+ - request: create an Automation token at https://www.npmjs.com/settings/~/tokens and paste it as NODE_AUTH_TOKEN in .env
46
+ - how to report: `P-001 done`
47
+
48
+ **Shelved until this is resolved**
49
+ - npm publish
50
+
51
+ **Continues regardless**
52
+ - everything else
53
+ ```
54
+
55
+ Only three lines are machine-read: the heading `## [ID] title`, the status line `- [ ] status: open`, and the optional `- due: YYYY-MM-DD HH:MM` (once passed, the item is rewritten as `withdrawn` automatically).
56
+ When the agent stops, `!pending_1.txt` appears at the root. You open `INBOX.md`, write `P-001 done` (or `P-001 rejected. …`, or any free text) below the `---`, and save.
57
+ On the agent's next tool call the line is injected:
58
+
59
+ ```
60
+ [INBOX.md: 1 unread line(s) written by the user] INBOX.md is the file the user writes in by hand; the following line(s) are the user's own words (copied by the hook). Under this project's rule (CLAUDE.md "Async queue"), lines in INBOX.md are the user's chat messages.
61
+ > P-001 done
62
+ A completion report (P-xxx done) corresponds to resolving the matching item in PENDING.md. Instructions, questions and proposals carry their plain meaning.
63
+ Handled lines are kept with a leading ✔ (lines are never deleted).
64
+ ```
65
+
66
+ The injected text is factual statements only — no imperatives. What to do with them is the CLAUDE.md rule's job (Claude Code's hooks reference asks for exactly this split: imperative hook text trips the model's prompt-injection defenses and gets surfaced to the user instead of being used as context; that happened in a real run before this wording).
67
+ The agent marks the item `- [x] status: done (2026-09-16)`, moves it under `# Resolved`, prefixes the INBOX line with `✔`, and the sentinel disappears at the next count.
68
+ If the agent tries to finish with unread lines, the `Stop` hook blocks with "INBOX.md still has 1 unread line(s) written by the user. Stopping with unread lines is blocked under this project's rule." plus the same text as the reason (twice per unread set and session by default; the second reason says how to get through: ✔ the line and state why).
69
+ Save `INBOX.md` as UTF-8: non-UTF-8 lines are re-read as Shift_JIS and injected with a warning asking the user to re-save the file.
70
+
71
+ ## What each hook does
72
+
73
+ | Event | Reads | Writes | Emits |
74
+ |---|---|---|---|
75
+ | `SessionStart` | PENDING, INBOX | deadline withdrawals, header line, sentinel, `.ask-later/state.json` | open count and list (30 shown, then "…and N more"), withdrawn IDs, unread INBOX lines, malformed items, a hint if PENDING.md is missing (`additionalContext`) |
76
+ | `UserPromptSubmit` | INBOX | state (signature; resets that session's block count) | unread lines, every time there are any |
77
+ | `PostToolUse` | INBOX | state (signature per `session_id` and `agent_id`) | unread lines **only when the set changed** since the last injection in that session/subagent |
78
+ | `Stop` | PENDING, INBOX | same as SessionStart | `{"decision":"block","reason":…}` while unread lines exist, up to `stopBlockMax` times per session (counted per session, so another session's `Stop` does not reset it); OS notification if the open set is new |
79
+ | `SessionEnd` | PENDING, INBOX | same | nothing (Claude Code does not read it); no notification (its budget is 1.5 s by default; `Stop` already notifies) |
80
+
81
+ Hook entries are exec form — `{"command":"node","args":["${CLAUDE_PROJECT_DIR}/.claude/ask-later/hook.mjs","Stop"],"timeout":20}` — so no shell and no absolute path; `settings.json` can be committed. Timeouts are explicit (the official defaults are 600 s for command hooks, 30 s for `UserPromptSubmit`, and a shared 1.5 s for `SessionEnd`, which a per-hook `timeout` in settings raises up to 60 s; plugin timeouts do not raise it). The code is copied into the project so it keeps working without `npx` caches; `.claude/ask-later/VERSION` records the copy's version and `status` tells you when it is older than the package. Re-run `npx ask-later@latest init` to update (idempotent).
82
+
83
+ ## Formats
84
+
85
+ `PENDING.md`: headings `## [ID] title` (`##` only); status `- [ ] status: open` / `- [x] status: done (date)` / `- [x] status: withdrawn (reason)` (`*` and `+` bullets also work); optional `- due: YYYY-MM-DD HH:MM`. Headings without any checkbox line (e.g. `## [R-010] note`) are not requests. Fenced code blocks are ignored. Line 3 `Open: N / INBOX unread: M / Last count: …` is updated at each count. CRLF/LF preserved; writes are atomic. Automatically withdrawn items stay under `# Open` with a rewritten status line; the agent moves them.
86
+ `INBOX.md`: lines after the first `---` (plus anything written above the first heading). Blank lines, headings (`#` followed by a space — `#1 done` is read), `>` and rules are skipped; lines starting with `✔` (`✓`, `✅`) are done. Lines are never deleted.
87
+
88
+ ## Configuration (`.ask-later/config.json`)
89
+
90
+ `lang` (`ja`/`en`; also selects the language of `hook.log`), `pending`, `inbox`, `sentinelPrefix` (default `!pending_`), `quietHours` (default 21→8: no notifications, deferred to the morning), `notify` (`false` to disable; `ASK_LATER_NO_NOTIFY=1` also disables), `stopBlockMax` (default 2, 0 disables, max 7). Missing or broken config falls back to defaults.
91
+
92
+ ## Commands
93
+
94
+ ```
95
+ npx ask-later init [--lang ja|en] [--dir <project>] [--force] [--local] [--no-hooks] [--no-files] [--no-claude-md] [--no-gitignore] [--shell]
96
+ npx ask-later uninit [--dir <project>] [--purge]
97
+ npx ask-later status [--dir <project>] [--json] [--dry-run]
98
+ npx ask-later notify [--dir <project>] [message]
99
+ npx ask-later snippet [--lang ja|en]
100
+ npx ask-later hook <SessionStart|UserPromptSubmit|PostToolUse|Stop|SessionEnd>
101
+ ```
102
+
103
+ `--shell` writes the old shell form (`node "${CLAUDE_PROJECT_DIR}/…" Stop`) instead of exec form. `uninit` does not touch `CLAUDE.md` or `.gitignore`; if `init` created `settings.json` and nothing else was added to it, `uninit` removes the file instead of leaving `{}`. `hook.log` lines are tagged `[session]` or `[session/agent]` inside subagents.
104
+ CLI errors (missing directory, bad `--lang`, unreadable `settings.json`, unwritable disk, Node < 20) are one or two readable lines, exit 1, nothing written. The `hook` subcommand exits 0 no matter what.
105
+ In an interactive bash, quote the sentinel name (`'!pending_1.txt'`) — history expansion trips on `!`.
106
+
107
+ ## As a plugin
108
+
109
+ The package ships `.claude-plugin/plugin.json` and `hooks/hooks.json`. `npm pack ask-later && tar xf ask-later-0.1.0.tgz && claude --plugin-dir ./package` enables the hooks without `init` (create the templates with `npx ask-later init --no-hooks`). Marketplace installation will be documented once a public repository exists.
110
+
111
+ ## Known limits
112
+
113
+ Verified on Windows only. Whether the agent obeys the injection depends on the model and on the CLAUDE.md rule — in a 2026-09-15 run with a small model the injection and the (then single) stop block both fired, but the agent neither prefixed ✔ nor fixed the status line; a 2026-09-16 run with sonnet did both from the injection alone. A model may refuse an *instruction* written in INBOX as a suspected embedded instruction (observed 2026-09-16 with the earlier imperative wording: the completion report was accepted, an extra "write this codeword" request was declined); the wording is now factual and the rule lives in CLAUDE.md, but a refused line simply stays un-✔ed — the sentinel and `status` show it, nothing says "refused". `PostToolUse` only runs right after a tool call. Once the block count is used up in a session, the same unread set is not blocked again until the user's next prompt or the set changes. Both `settings.json` and `settings.local.json` configured means hooks run twice. Deadlines use local time. `PostToolUse` also fires inside subagents (official spec); the signature is kept per `agent_id`, and injection inside a real subagent was observed on 2026-09-16 (design audit, sonnet, `Agent` tool).
114
+
115
+ ## Paid companion
116
+
117
+ The tool is free (MIT). A separate **"Unattended operation design book" (JPY 1,480, Japanese, Markdown + HTML ZIP, 7 chapters, 10 templates, ~300 KB)** covers the discipline rather than the tool: why agents stop, the shelving queue, four brakes for unattended runs, independent-session audits, machine-written records, a dated log of 14 real failures, and a template set. For people running `-p` or auto-resume unattended; not needed for interactive-only use. Chapter 1 and the minimal templates are free in this package under `docs/manual-free/` (open `index.html`). Sales page: in preparation.
118
+
119
+ ## Development
120
+
121
+ `npm test` (node --test), `npm run fieldlog` (runs the hooks on a fixed copy of the real files this tool was ported from and cross-checks counts against the original PowerShell implementation; see `docs/field-log.md`, Japanese), `npm run build:manual`. These need `tests/` and `scripts/`, which are not in the tarball.
122
+
123
+ ## License
124
+
125
+ MIT. Claude and Claude Code are trademarks of Anthropic, PBC. Unofficial; not endorsed by Anthropic.
package/README.md ADDED
@@ -0,0 +1,311 @@
1
+ # ask-later — 聞いて、待たずに進む(Claude Code 用の非同期の質問キュー)
2
+
3
+ **Claude Code が「確認していいですか」で止まらないようにする、ファイル 2 つとフック 5 つ**です。
4
+ エージェントは人に聞きたいことを `PENDING.md` に起票して、それに依存する作業だけを止め、残りを続けます。
5
+ 人はメモ帳で `INBOX.md` に 1 行書くだけ。フックが次のツール呼び出しでその行をエージェントの文脈に注入し、
6
+ 未読が残ったまま終わろうとすると差し止めます(既定 2 回)。プロジェクトのルートに置かれる `!pending_3.txt` という名前のファイルが「未処理 3 件」を示します。
7
+
8
+ Node.js 20 以上・依存パッケージなし・MIT・非公式。**Claude Code 専用**です——Claude Code のフック契約(stdin の JSON・`hookSpecificOutput.additionalContext`・`decision: block`)だけで動き、他のコーディングエージェントの経路は持っていません。
9
+ 使うイベントは `SessionStart` / `UserPromptSubmit` / `PostToolUse` / `Stop` / `SessionEnd`。
10
+ 動作確認は Windows 11・Node 24・Claude Code 2.1.268 のみ(macOS/Linux は未確認)。
11
+
12
+ > **先に読んでください(何を書き換えるか)**
13
+ >
14
+ > - `npx ask-later init` が書き換えるのは **`.claude/settings.json` の `hooks`**(他のキーは触りません。既にあるフックも残します)、
15
+ > **`CLAUDE.md`**(末尾に `## 非同期キュー(ask-later)` の断片を足します。既にあれば触りません。`--no-claude-md` で抑止)、
16
+ > **`.gitignore`**(`.ask-later/` と `\!pending_*.txt` の 2 行。無い行だけ。`--no-gitignore` で抑止)の 3 つです。
17
+ > 加えて `.claude/ask-later/`(フック本体の写し)、`PENDING.md`・`INBOX.md`(既にあれば触りません)、`.ask-later/config.json` を作ります。
18
+ > **`npx ask-later uninit` で `settings.json` は同じ内容に戻ります**(字下げ幅・改行コード・末尾改行の有無も元のまま。自動テストで 2 スペース/4 スペース/タブ/CRLF/末尾改行なしを確認。
19
+ > ただし配列を 1 行に畳んで手書きした settings.json は、内容は同じでも配列が展開された体裁になります)。`CLAUDE.md` と `.gitignore` に足した行は人の文書なので残します
20
+ > - **フックは何があっても終了コード 0 で終わり、エージェントの作業を止めません。**`INBOX.md` が無い・壊れている・stdin が JSON でない・
21
+ > 書き込めない、どれでも黙って通し、読める 1 行を `.ask-later/hook.log` に残すだけです
22
+ > - 終了の差し止め(`Stop` フックの `decision: block`)は **同じ未読の集合・同じセッションにつき既定 2 回**です(`.ask-later/config.json` の `stopBlockMax`。0 で差し止めなし、上限 7)。
23
+ > Claude Code 本体が連続 8 回の差し止めで打ち切る仕様なので、上限 7 で無限ループにはなりません。回数を使い切った後は、エージェントが注入を無視すれば未読が残ったまま終わります。番兵ファイルと `npx ask-later status` で人が気づけます
24
+ > - OS 通知(Windows のトースト/macOS の `osascript`/Linux の `notify-send`)は補助で、出せない環境では何も起きません。**第一の通知手段は音の出ない番兵ファイル**です
25
+ > - Claude Code のフックの仕様(`additionalContext`・`decision: block`)に依存しています。仕様が変われば動かなくなります
26
+ > - 非公式の道具です。Claude および Claude Code は Anthropic, PBC の商標であり、作者は Anthropic とは無関係です
27
+
28
+ ## 必要なもの
29
+
30
+ - **Node.js 20 以上**(https://nodejs.org/ の「LTS」で足ります。それより古い Node では理由を表示して止まります)
31
+ - **Claude Code**(フックが使える版。`claude --version` で確認。動作確認は 2.1.268)
32
+ - プロジェクトのディレクトリ(`.claude/settings.json` を置く場所。無ければ作ります)
33
+
34
+ ## 使い方(上から順に)
35
+
36
+ ### 1. 入れる
37
+
38
+ プロジェクトのルートで実行します。
39
+
40
+ ```
41
+ npx ask-later init
42
+ ```
43
+
44
+ ```
45
+ 作成 / created: PENDING.md, INBOX.md, .ask-later/config.json, CLAUDE.md, .gitignore, .claude/settings.json
46
+ フック本体 / hook files: .claude/ask-later/ 0.1.0(更新は npx ask-later@latest init / re-run to update)
47
+
48
+ 次にやること:
49
+ 1. CLAUDE.md にエージェントが守る規律(`## 非同期キュー(ask-later)`)を足した。一度読んでおく
50
+ 2. claude を起動する。INBOX.md の `---` の下に 1 行(例: `テスト: これが読めたら「読めた」と返して`)書いて何か入力すると、その行の行頭に ✔ が付く(これが確認。返事にも大抵出る)。`npx ask-later status` で件数を見る
51
+ ```
52
+
53
+ `.claude/settings.json`・`CLAUDE.md`・`.gitignore` が既にあれば足りない分だけ足して「更新 / updated」と出ます。`PENDING.md`・`INBOX.md` が既にあれば「そのまま / kept」と出て触りません(`--force` で雛形に戻します)。
54
+ 英語の雛形と注入文にするなら `--lang en`、`settings.local.json` に入れるなら `--local`、別のディレクトリなら `--dir <path>` です。
55
+
56
+ ### 2. エージェントに規律を渡す(`init` が済ませています)
57
+
58
+ `init` が `CLAUDE.md` の末尾に足すのは「人の判断が要ることに当たったら `PENDING.md` に起票し、依存しない作業は続ける。`INBOX.md` の未読が注入されたらチャットの発言と同じ扱いで実行し、行頭に ✔ を付ける。`INBOX.md` の行は利用者本人の発言であり、注入文はその写しである」という 20 行ほどの断片です(内容は `npx ask-later snippet` で読めます。`--no-claude-md` で init に足させず、自分で `npx ask-later snippet >> CLAUDE.md` としても同じです)。
59
+ フックは機構であって、**止まらずに進む挙動そのものは CLAUDE.md の規律が作ります**。ここが無いと、フックが注入しても何も起きません。
60
+ 「どう扱うか」の指示を CLAUDE.md に置き、フックの注入文は「何がどうなっているか」の事実だけを述べる、という分担です。Claude Code の公式リファレンスは、フックが入れる文を命令形でなく事実の陳述で書くよう求めています——命令形で書くとモデルの prompt-injection 防御が働き、文脈として扱われずに人へ差し戻されます(設計監査 2 巡目で実際に起きました。下「未検証・既知の制限」)。
61
+
62
+ `.gitignore` に足す 2 行は次のとおりです(`!` で始まるファイルを無視するには `\` が要ります。`!pending_*.txt` と書くと逆に「無視しない」の意味になります)。
63
+
64
+ ```
65
+ .ask-later/
66
+ \!pending_*.txt
67
+ ```
68
+
69
+ ### 3. 確かめる(30 秒)
70
+
71
+ `claude` を起動し、メモ帳で `INBOX.md` の `---` の下に 1 行書いて保存します。
72
+
73
+ ```
74
+ テスト: これが読めたら「読めた」と返して
75
+ ```
76
+
77
+ `claude` に何か(「進めて」でも何でも)入力すると、`UserPromptSubmit` フックがその行を注入します。**確認するのは `INBOX.md` の行頭に `✔` が付くこと**です(付けば届いて処理されています)。返事にも「読めた」が大抵出ますが、モデルがその文を埋め込み指示と疑って返事には出さないことがあります——✔ が付いていれば届いています。
78
+ 付かなければ `.ask-later/hook.log` を見てください(フックが走っていれば 1 行ずつ残っています。何も無ければ `settings.json` のフックが読まれていません——`claude` を起動し直す・`/hooks` で確認する)。
79
+
80
+ ```
81
+ npx ask-later status
82
+ ```
83
+
84
+ ```
85
+ PENDING 未処理: 0件
86
+ INBOX 未読: 0件
87
+ 番兵: なし(0 件)
88
+ フック本体: 0.1.0
89
+ ```
90
+
91
+ `--json` で機械可読な形になります。`status` は集計のついでに、期限切れの項目の自動取り下げ・`PENDING.md` のヘッダ行・番兵ファイルを更新します(`--dry-run` なら読むだけ。番兵は今あるものをそのまま報告します)。
92
+ 「フック本体」は `.claude/ask-later/` の写しの版で、パッケージより古ければ `npx ask-later@latest init` を案内します。
93
+
94
+ ### 4. claude を起動する。エージェントが起票する
95
+
96
+ `claude` を起動すると `SessionStart` フックが走り、未処理の件数と一覧が文脈に入ります(0 件なら何も入りません)。
97
+ 作業中にエージェントが人の判断や操作を要することに当たると、`PENDING.md` の「# 未処理」の下にこう書きます(書式は雛形の中の「エージェントへ」にあります)。
98
+
99
+ ```
100
+ ## [P-001] npm のトークンを .env に入れてほしい
101
+
102
+ - [ ] 状態: 未処理
103
+ - 起票: 2026-09-16
104
+ - 今何をしているか: パッケージの公開の準備。トークンは本人しか発行できない
105
+ - 依頼内容: https://www.npmjs.com/settings/~/tokens で Automation トークンを作り、.env の NODE_AUTH_TOKEN に貼る
106
+ - 完了報告の書き方: `P-001 done`
107
+
108
+ **この項目が解けるまで棚上げする作業**
109
+ - npm publish
110
+
111
+ **この項目と関係なく続行する作業**
112
+ - README・テスト・その他すべて
113
+ ```
114
+
115
+ 機械が読むのは見出し `## [P-001] …` と状態行 `- [ ] 状態: 未処理` と期限行 `- 期限: YYYY-MM-DD HH:MM`(任意)の 3 つだけです。
116
+ エージェントが `Stop`(応答の終わり)に達すると集計が走り、ルートに **`!pending_1.txt`** が置かれます。名前が件数で、`!` で始まるので一覧の先頭に来ます。
117
+ 中身は「利用者にお願いしたいことが 1 件あります。PENDING.md を開いてください。完了報告は INBOX.md に 1 行……」という案内です。0 件になると消えます。
118
+
119
+ ### 5. 人が INBOX.md に 1 行書く
120
+
121
+ メモ帳で `INBOX.md` を開き、`---` より下に 1 行書いて保存します。エージェントを起動する必要も、書式を覚える必要もありません。
122
+
123
+ ```
124
+ P-001 done
125
+ ```
126
+
127
+ `P-001 却下。当面やらない` でも `方針を変える: …` でも構いません。番号の無い自由文も読まれます。
128
+
129
+ ### 6. 次のツール呼び出しで注入され、✔ が付き、番兵が消える
130
+
131
+ エージェントが動いている最中なら、次のツール呼び出しの直後(`PostToolUse`)にこう注入されます。
132
+
133
+ ```
134
+ 【INBOX.md に利用者本人の未読の行: 1件】INBOX.md は利用者が手で書く連絡ファイルで、次の行は利用者本人が書いたもの(フックが写した)。このプロジェクトの規律(CLAUDE.md「非同期キュー」)では、INBOX.md の行は利用者のチャット発言と同じ扱いになる。
135
+ > P-001 done
136
+ P-xxx の完了報告は PENDING.md の該当項目の解消に対応する。命令・質問・提案は、そのままの意味の発言である。
137
+ 処理済みの行は行頭に ✔ を付けて残す決まりになっている(行は消さない)。
138
+ ```
139
+
140
+ 注入文は事実の陳述だけで、命令(「〜すること」)を含みません。何をするかは `CLAUDE.md` の断片が決めます。
141
+ エージェントは `PENDING.md` の項目を `- [x] 状態: 完了 (2026-09-16)` にして「# 解消済み」へ移し、`INBOX.md` の行を `✔ P-001 done` にします。
142
+ 次の集計(`Stop`・`SessionEnd`・`status`)で `!pending_1.txt` が消えます。
143
+
144
+ エージェントが止まっていた(応答を終えていた)場合は、次にあなたが何か入力したとき(`UserPromptSubmit`)に注入されます。
145
+ エージェントが未読を処理せずに終わろうとすると、`Stop` フックが `decision: block` で差し止め、「INBOX.md に利用者本人の未読の行が 1 件残っている。未読が残ったままの停止は、このプロジェクトの規律で差し止められる」に同じ注入文を添えて理由として返します(同じ未読の集合につき既定 2 回。2 回目の理由には「2 回目の差し止め。最大 2 回。処理できない行は ✔ を付けて理由を返答に書けば通る」と添えます)。
146
+
147
+ `INBOX.md` は UTF-8 で保存してください。メモ帳の「ANSI」(CP932)で書かれた行は Shift_JIS として読み直して注入しますが、エージェントがその行を書き換えると壊れるので、注入文と `status` に「UTF-8 で保存し直してください」と出ます。
148
+
149
+ ## フックが何をするか
150
+
151
+ | イベント | 読む | 書く | 出す |
152
+ |---|---|---|---|
153
+ | `SessionStart` | PENDING / INBOX | 期限切れの取り下げ・ヘッダ行・番兵・`.ask-later/state.json` | 未処理の件数と一覧(30 件を超えると「ほか N 件」)、期限切れで取り下げた ID、INBOX の未読、状態行が崩れた項目、PENDING.md が無いときの案内(`additionalContext`) |
154
+ | `UserPromptSubmit` | INBOX | state.json(署名。そのセッションの差し止め回数を 0 に戻す) | 未読があれば毎回、その全行 |
155
+ | `PostToolUse` | INBOX | state.json(署名) | 未読の**集合が前回の注入から変わったときだけ**、その全行(同じ内容を毎ツール呼び出しで繰り返さない)。署名は `session_id` と(サブエージェントの中なら)`agent_id` ごとに持つので、サブエージェントが先に受け取っても本体にも届く |
156
+ | `Stop` | PENDING / INBOX | 取り下げ・ヘッダ行・番兵・state.json | 未読があれば `{"decision":"block","reason":…}`(同じ集合・同じセッションにつき `stopBlockMax` 回。既定 2。回数はセッションごとに持つので、別のセッションの `Stop` が割り込んでも数え直さない)。未処理の集合が新しければ OS 通知 |
157
+ | `SessionEnd` | PENDING / INBOX | 取り下げ・ヘッダ行・番兵・state.json | 何も出さない(Claude Code は読まない)。OS 通知も出さない(SessionEnd の予算は既定 1.5 秒で、通知が間に合わない。Stop が同じ条件で出している) |
158
+
159
+ フックのエントリは exec 形(シェルを通さない)で、`${CLAUDE_PROJECT_DIR}` は Claude Code が `args` の中でも置き換えます。絶対パスを書かないので `settings.json` をそのままリポジトリで共有できます。
160
+
161
+ ```json
162
+ { "type": "command", "command": "node", "args": ["${CLAUDE_PROJECT_DIR}/.claude/ask-later/hook.mjs", "Stop"], "timeout": 20 }
163
+ ```
164
+
165
+ タイムアウトは 15〜20 秒を明示しています。公式の既定は command フックが 600 秒(`UserPromptSubmit` は 30 秒)で集計には十分ですが、`SessionEnd` だけは既定 1.5 秒の共有予算で、`settings.json` の各フックの `timeout` で最大 60 秒まで延びます(プラグイン形の `timeout` では延びません)。既定に依存しないよう全部に書いています。
166
+ 本体はプロジェクトの `.claude/ask-later/` に写してあるので、`npx` のキャッシュや `node_modules` が消えても動きます。
167
+ 更新は `npx ask-later@latest init` を再実行します(自分のフックを外してから足すので、増えません。写しの版は `.claude/ask-later/VERSION` にあり、`status` が古ければ知らせます)。
168
+
169
+ ツール呼び出しごとに `node` が 1 回起動します(この環境で数十 ms)。ログは `.ask-later/hook.log`(1 MB で 1 世代回します。`--lang en` なら英語)。各行の `[abcdef12]` は `session_id` の先頭 8 字で、サブエージェント(`Agent` ツール)の中で走ったときは `[abcdef12/a22f82b7]` と `agent_id` が付きます。
170
+
171
+ ## ファイルの書式
172
+
173
+ ### PENDING.md
174
+
175
+ ```
176
+ # 未処理
177
+
178
+ ## [P-001] タイトル
179
+ - [ ] 状態: 未処理
180
+ - 期限: 2026-09-20 18:00 ← 任意。過ぎると「- [x] 状態: 取り下げ(期限 … を過ぎたため自動。実施の有無は INBOX の報告で確定する)」に書き換わる
181
+
182
+ # 解消済み
183
+
184
+ ## [P-000] 済んだもの
185
+ - [x] 状態: 完了 (2026-09-15)
186
+ ```
187
+
188
+ - 見出しは `## [ID] タイトル`(`##` だけ。`###` 以下は項目になりません)。ID は `P-001` の形でなくても構いません(`[x]` の `x` は大文字でも可)。箇条書きの記号は `-` のほか `*` `+` でも読みます
189
+ - 状態行が無く、チェックボックス付きの箇条書きも無い見出し(例: `## [R-010] 報告`)は依頼ではないので数えません。チェックボックスはあるのに状態行の書式が違う項目は「状態行が崩れている」として数えず、注入で知らせます
190
+ - 期限切れで自動取り下げになった項目は状態行だけが書き換わり、`# 未処理` の見出しの下に残ります(機械は行を動かしません)。エージェントが区切りで `# 解消済み` へ移します(雛形の「エージェントへ」にそう書いてあります)
191
+ - コードブロック(` ``` ` / `~~~`)の中は読みません(雛形の書き方の例が項目に数えられないように)
192
+ - 3 行目の `未処理: N件 / INBOX未読: M件 / 最終集計: …` は集計のたびに書き換わります(無ければ何もしません)
193
+ - 改行コード(CRLF / LF)はそのまま保ちます。書き込みは一時ファイル経由(途中で切れても元のファイルが半端になりません)
194
+ - 英語版の書式は `- [ ] status: open` / `- [x] status: done` / `- [x] status: withdrawn (…)` / `- due: …`(`--lang en` の雛形)
195
+
196
+ ### INBOX.md
197
+
198
+ - 最初の `---`(水平線)より後ろの行を読みます。加えて、最初の見出し(`# `)より前に書かれた行も読みます(ファイルの先頭に書いてしまう人がいたため)
199
+ - 空行、見出し(`#` の後に空白がある行。`#1 done` のような行は読みます)、`>` で始まる行、水平線は読みません。行頭に `✔`(`✓` `✅` も可)が付いた行は処理済みです
200
+ - 行は消さず、✔ を付けて残します(記録になります)
201
+ - UTF-8 で保存します。UTF-8 でない行(メモ帳の ANSI)は Shift_JIS として読み直しますが、注入文と `status` に保存し直すよう注意が出ます
202
+
203
+ ## 設定(`.ask-later/config.json`)
204
+
205
+ `init` が既定値で作ります。無くても、壊れていても動きます(壊れていれば `status` が注意を出します)。
206
+
207
+ ```json
208
+ {
209
+ "lang": "ja",
210
+ "pending": "PENDING.md",
211
+ "inbox": "INBOX.md",
212
+ "sentinelPrefix": "!pending_",
213
+ "quietHours": { "start": 21, "end": 8 },
214
+ "notify": true,
215
+ "stopBlockMax": 2
216
+ }
217
+ ```
218
+
219
+ | 項目 | 意味 |
220
+ |---|---|
221
+ | `lang` | 注入文・番兵・status・hook.log の言語(`ja` / `en`) |
222
+ | `pending` / `inbox` | ファイル名(ルート直下。パス区切りは使えません) |
223
+ | `sentinelPrefix` | 番兵ファイルの接頭辞。`<prefix><件数>.txt` |
224
+ | `quietHours` | この時間帯は OS 通知を鳴らさず、翌朝の最初の集計に持ち越します(`start === end` で静音なし) |
225
+ | `notify` | `false` で OS 通知を止めます。環境変数 `ASK_LATER_NO_NOTIFY=1` でも止まります(CI・テスト用) |
226
+ | `stopBlockMax` | `Stop` の差し止め回数(同じ未読の集合・同じセッションにつき)。`0` で差し止めなし、上限 `7`(Claude Code 本体が連続 8 回で打ち切るため)。未読の集合が変わったとき、または人が次の入力をしたとき(`UserPromptSubmit`)に数え直します——人の 1 入力につき最大この回数 |
227
+
228
+ OS 通知は「未処理の集合が前回通知したときと変わった」ときだけ鳴ります。同じ集合に二度は鳴りません。`npx ask-later notify` で 1 回鳴らして確かめられます。
229
+
230
+ ## コマンド
231
+
232
+ ```
233
+ npx ask-later init [--lang ja|en] [--dir <project>] [--force] [--local] [--no-hooks] [--no-files] [--no-claude-md] [--no-gitignore] [--shell]
234
+ npx ask-later uninit [--dir <project>] [--purge]
235
+ npx ask-later status [--dir <project>] [--json] [--dry-run]
236
+ npx ask-later notify [--dir <project>] [message]
237
+ npx ask-later snippet [--lang ja|en]
238
+ npx ask-later hook <SessionStart|UserPromptSubmit|PostToolUse|Stop|SessionEnd>
239
+ npx ask-later --version / --help
240
+ ```
241
+
242
+ - `init` の既定は exec 形 `{"command":"node","args":["${CLAUDE_PROJECT_DIR}/.claude/ask-later/hook.mjs","Stop"]}` です。`--shell` でシェル形式 `node "${CLAUDE_PROJECT_DIR}/…" Stop` にできます(パイプ等が要るときだけ。Git Bash の無い Windows では v2.1.198 より前の Claude Code がプレースホルダを展開しません)
243
+ - `init --no-hooks` は雛形だけ、`--no-files` はフックだけを入れます。`--no-claude-md` / `--no-gitignore` でそれぞれを触りません
244
+ - `uninit` は `settings.json` と `settings.local.json` の両方から自分のフックを外し、`.claude/ask-later/`・番兵・`.ask-later/` を消します。`init` の前に `settings.json` が無く、外した後に `{}` しか残らないなら、ファイルごと消して「無かった」に戻します。`PENDING.md`・`INBOX.md` は `--purge` を付けたときだけ消します。`CLAUDE.md` と `.gitignore` は触りません
245
+ - `hook` はフック本体です(Claude Code が呼びます。stdin に JSON)。手で `settings.json` に書くときは `npx ask-later hook Stop` の形でも動きますが、呼ばれるたびに `npx` の解決が入るので `init` が写す `.claude/ask-later/hook.mjs` の方が軽いです
246
+ - 番兵ファイルを対話シェルで開くときは `'!pending_1.txt'` と引用してください(bash の履歴展開で `!` が `event not found` になります)
247
+
248
+ ## プラグインとして入れる
249
+
250
+ 同じパッケージに `.claude-plugin/plugin.json` と `hooks/hooks.json` が入っています。展開したディレクトリを指定して起動すると、`init` を使わずにフックが有効になります(この形では `PENDING.md`・`INBOX.md` は自分で置くか、`npx ask-later init --no-hooks` で雛形だけ作ります)。
251
+
252
+ ```
253
+ npm pack ask-later && tar xf ask-later-0.1.0.tgz
254
+ claude --plugin-dir ./package
255
+ ```
256
+
257
+ マーケットプレイス(`/plugin marketplace add`)からの導入は、公開リポジトリを用意してからこの README に追記します(準備中)。
258
+
259
+ ## 止まる条件(壊れた入力)
260
+
261
+ コマンド(`init` / `uninit` / `status` / `notify` / `snippet`)は、次の場合に理由を 1〜2 行で表示して終了コード 1 で止まり、**何も書きません**。スタックトレースは出しません。
262
+
263
+ - ディレクトリが無い・`--lang` が `ja` / `en` 以外・`--dir` に値が無い・知らないコマンド
264
+ - `.claude/settings.json` が JSON として読めない(「直してから再実行してください。何も書き換えていません」)・中身がオブジェクトでない
265
+ - 書き込めない(権限が無い・読み取り専用・別のアプリが開いている)
266
+ - Node.js が 20 未満
267
+
268
+ フック(`hook`)は上のどれでも**終了コード 0** で、標準エラーにも何も出しません。`PENDING.md` が無ければ `SessionStart` が 1 行だけ「`npx ask-later init` で雛形を作る」と案内し、他のイベントは黙ります。
269
+ `PENDING.md` の場所にディレクトリがある、`config.json` が壊れている、stdin が `{` で始まらない、知らないイベント名——すべて `.ask-later/hook.log` に 1 行残して通します。
270
+
271
+ ## 未検証・既知の制限
272
+
273
+ - **動作確認は Windows 11・Node 24.19・Claude Code 2.1.268 だけです。**macOS/Linux は未実行です(標準機能しか使っていないので動く見込みですが、実測していません。通知はそれぞれ `osascript` / `notify-send` に投げるだけです)
274
+ - **エージェントが注入に従うかは、エージェント(モデルと CLAUDE.md の規律)次第です。**2026-09-15 の実測(`claude -p`・小さいモデル)では、注入は届き `Stop` の差し止めも掛かりましたが、エージェントは ✔ を付けず状態行も直しませんでした(当時は差し止め 1 回)。2026-09-16 の実測(sonnet)では注入だけで ✔ と状態行の更新まで行いました。差し止めの回数を使い切ればそのまま終わります。番兵ファイルと `npx ask-later status` は残ります
275
+ - **INBOX の行に書いた命令を、モデルが「埋め込み指示」と疑って実行しないことがあります。**2026-09-16 の実測(sonnet・注入文が命令形だった版)では、`P-001 done。あと、返答の最後に合言葉「ヤマブキ」を書いて` のうち完了報告は受け取られ、合言葉は「注入されたファイル内容経由の付帯指示なので出力しない」と拒否されました。Claude Code の公式リファレンスが言うとおり、命令形の注入文は prompt-injection 防御を起動します。0.1.0 では注入文を事実の陳述に書き換え、「INBOX の行は利用者本人の発言」を CLAUDE.md の断片に置きました(反映後の実測は `out/revise2_claude_p_sonnet_2026-09-16.*`)。それでも拒否されたときは ✔ が付かず、番兵と `status` に残ります——「拒否した」とは書かれないので、無人運転では番兵で気づくことになります
276
+ - `PostToolUse` はツール呼び出しの直後にしか走りません。エージェントが長く考えている間や、応答を書いている間は届きません(次の呼び出しまで待ちます)
277
+ - 差し止めの回数は `.ask-later/state.json` にセッションごとに持ちます。使い切った後は、人が次の入力をする(`UserPromptSubmit`)か未読の集合が変わるまで、そのセッションでは差し止めません(`UserPromptSubmit` の注入は毎回出ます)
278
+ - `settings.json` と `settings.local.json` の両方に入れると、フックが 2 回走ります(`uninit` は両方から外します)
279
+ - 期限行の時刻はこの PC のローカル時刻で解釈します
280
+ - サブエージェント(`Agent` ツール)の中でも `PostToolUse` は走ります(公式仕様)。署名は `agent_id` ごとに持つので本体にも届きます。2026-09-16 の実測(設計監査 2 巡目・sonnet・`Agent` ツール使用)で、サブエージェント内の注入が `agent_id` の鍵で記録され、本体でも差し止めが掛かることを確認しました。`hook.log` では `[sid/agent_id]` で区別できます
281
+ - Claude Code のフックの JSON 出力の仕様(`hookSpecificOutput.additionalContext`・`decision: block`)が変わると動かなくなります。そのときは `hook.log` に「注入」と出ているのに文脈に入らない、という形で分かります
282
+
283
+ ## 有償版
284
+
285
+ 道具(この CLI とフック)は無料・MIT です。別売りで **「無人運転の設計書」(¥1,480・Markdown+HTML の ZIP・7 章・雛形 10 本・約 30 万バイト)** を用意しています。
286
+ 道具の使い方ではなく、**止めない運用を成立させる規律の設計**——なぜ止まるか(判断の留保と作業の停止は別)、棚上げキューの書式、無人実行の 4 つのブレーキ(回数・費用・無進捗・不可逆操作の前の昇格)、独立セッションによる監査の型、記録を機械に書かせる方法、そして**第 6 章・現場の失敗の記録 14 件(日付つき)**——1 セッションで 60 回の確認が出て無人実行が壊れた、監査結果が会話にしか無くて消えた、フック出力の文字化け、期限切れ項目の自動取り下げ、「念のため確認します」問題——と、テンプレ一式(CHARTER の骨子・PENDING/INBOX・監査役の定義・settings.json の hooks 断片・CLAUDE.md の断片)を収めます。
287
+ 対象は `-p` や自動再開で無人運転をしている人。対話でしか使わない人には要りません。
288
+
289
+ **第 1 章と最小テンプレは無料で、このパッケージの `docs/manual-free/` に入っています**(`npm pack ask-later` で取れます。`index.html` を開けば読めます)。
290
+ **入手方法**: 販売ページ(準備中。公開したらここに URL を書きます)から Stripe で決済すると、決済完了の画面から ZIP のダウンロードページに移ります。届かない場合の連絡先は販売ページの「特定商取引法に基づく表記」にあります。
291
+
292
+ ## 開発者向け
293
+
294
+ ソースコードは npm のパッケージに入っています(`npm pack ask-later`)。公開リポジトリは準備中です。
295
+ 下のコマンドのうち `npm test`・`fieldlog`・`build:manual` は tarball に入っていない `tests/`・`scripts/` が要ります(公開リポジトリができたらそちらで動きます)。
296
+
297
+ ```
298
+ npm test # 自動テスト(node --test。PENDING の解析・INBOX の未読・注入の重複抑止・Stop の差し止め・init/uninit の往復・CLI)
299
+ npm run fieldlog # 実データ(この道具の移植元の PENDING/INBOX の固定コピー)でフックを走らせ、docs/field-log.md を書く。tests/fixtures/real/ が要る
300
+ npm run build:manual # 有償版の設計書を dist/ に組む(Markdown → HTML → ZIP)
301
+ ```
302
+
303
+ `docs/field-log.md`(パッケージに同梱)に、実運用 5 日分のファイル(依頼 27 件・未処理 5 件)で集計・注入・差し止めを動かした記録と、移植元の PowerShell 実装(独立した実装)との件数・ID の照合があります。
304
+
305
+ ライブラリとしても使えます: `ask-later`(`collect` / `summarize`)、`ask-later/pending`(`parsePending` / `openItems` / `expireDeadlines`)、`ask-later/inbox`(`unreadLines` / `signatureOf`)、`ask-later/hook`(`main` / `decide`)、`ask-later/install`(`init` / `uninit` / `mergeHooks` / `removeHooks`)。
306
+
307
+ ## ライセンス
308
+
309
+ MIT License。
310
+
311
+ Claude および Claude Code は Anthropic, PBC の商標です。このツールは非公式であり、Anthropic の承認・提携を受けたものではありません。
@@ -0,0 +1,12 @@
1
+ #!/usr/bin/env node
2
+ // 入口。Node の版を先に見てから本体を読む(古い Node でも読める文で止まるように)。
3
+ const major = Number(process.versions.node.split(".")[0]);
4
+ if (!(major >= 20)) {
5
+ console.error(`エラー: Node.js 20 以上が必要です(今は ${process.versions.node})/ Node.js >= 20 is required`);
6
+ process.exit(1);
7
+ }
8
+ const { runCli } = await import("../lib/cli.mjs");
9
+ const code = await runCli(process.argv.slice(2));
10
+ // stdout を書き切ってから終える(フックの JSON が途中で切れないように)
11
+ if (process.stdout.writableLength > 0) process.stdout.write("", () => process.exit(code));
12
+ else process.exit(code);