discuss

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

discuss — Interactive markdown review session

discuss — 交互式Markdown评审会话

Open markdown content in
discuss
, watch the user drop comments and replies, and respond with takes — the agent's view on each question or thread. Takes are semantically distinct from replies: the human types replies in the browser; the agent posts takes via the API.
The source can be either a file on disk or markdown piped in on stdin (e.g. an ad-hoc summary of a staged diff that the agent generates and pipes straight into discuss without writing to disk).
discuss
中打开Markdown内容,监控用户添加的评论与回复,并通过发布takes(Agent对每个问题或线程的观点)做出响应。Takes与回复在语义上有所区别:人类在浏览器中输入回复;Agent通过API发布takes。
内容来源可以是磁盘上的文件,也可以是通过stdin管道传入的Markdown(例如,Agent生成的暂存差异临时摘要,直接通过管道传入discuss而不写入磁盘)。

Arguments

参数

  • $ARGUMENTS
    — Either a path to the markdown file to review, OR markdown content the user wants to review without writing it to disk. If missing and the user has not described the content, ask which file/content and stop.
  • $ARGUMENTS
    — 可以是待评审的Markdown文件路径,也可以是用户想要评审但不想写入磁盘的Markdown内容。如果该参数缺失且用户未描述内容,请询问具体文件/内容并停止操作。

Stdin mode

stdin模式

When you have markdown content already in hand (e.g. a generated summary of staged changes) and don't need it on disk, pipe it in instead of writing a temp file:
  • discuss -
    reads markdown from stdin explicitly.
  • <some-command> | discuss
    also reads stdin (auto-detected when no file arg is given and stdin is not a TTY).
In stdin mode, the
session.started
event reports
source_file: "<stdin>"
and history archives are written under
.../unnamed/
since there is no source path to derive a folder name from.
当你已经持有Markdown内容(例如,暂存变更的生成摘要)且不需要将其写入磁盘时,可通过管道将其传入,而非写入临时文件:
  • discuss -
    显式从stdin读取Markdown内容。
  • <some-command> | discuss
    同样会读取stdin(当未指定文件参数且stdin不是TTY时自动检测)。
在stdin模式下,
session.started
事件会报告
source_file: "<stdin>"
,历史归档会写入
.../unnamed/
目录下,因为没有源路径可用于生成文件夹名称。

Multi-file mode

多文件模式

Pass several paths to review them together in one session with a file sidebar:
discuss plan.md design.md notes.md
  • Files are identified by
    fileId
    (
    f-1
    ,
    f-2
    , … in CLI order).
    /api/state
    includes a
    files
    array (
    {id, path, kind}
    ).
  • Every
    thread.created
    payload carries a
    fileId
    . When you create threads or push source updates in a multi-file session,
    fileId
    is required — omitting it returns
    400 missing_file_id
    .
  • Anchor indices are per-file (1-based commentable blocks within that file's document).
  • session.started
    gains
    files_count
    , and
    source_file
    becomes
    multi-<N>-files
    .
传入多个路径,即可在一个会话中同时评审多个文件,会话会显示文件侧边栏:
discuss plan.md design.md notes.md
  • 文件通过
    fileId
    标识(按CLI顺序为
    f-1
    f-2
    ……)。
    /api/state
    包含
    files
    数组(
    {id, path, kind}
    )。
  • 每个
    thread.created
    负载都会携带
    fileId
    。在多文件会话中创建线程或推送源更新时,
    fileId
    必填项 —— 省略该参数会返回
    400 missing_file_id
    错误。
  • 锚点索引按文件划分(每个文件文档中可添加评论的块元素为1-based索引)。
  • session.started
    会新增
    files_count
    字段,
    source_file
    字段变为
    multi-<N>-files

Diff review mode

差异评审模式

Prefer
discuss diff
over generating a markdown wrapper of a git diff.
It skips the summarize-and-fence round trip entirely — the binary runs
git diff
, splits it per file, and renders each hunk as a
diff-<lang>
block with line-anchored threads working out of the box:
discuss diff                  # staged (git diff --cached)
discuss diff --unstaged       # working tree
discuss diff HEAD~3..HEAD     # arbitrary range forwarded to git diff
discuss plan.md diff          # markdown file(s) + diff in one session
  • session.started
    gains
    mode
    (
    "markdown"
    /
    "diff"
    /
    "mixed"
    ) and
    git_args
    so you know what's under review.
  • Each changed file is its own sidebar entry with its own
    fileId
    ; per-file prose is optional — post takes on file threads when intent needs explaining, stay silent on mechanical changes.
  • Diff output is capped at 5 MB (
    --max-diff-bytes
    /
    DISCUSS_MAX_DIFF_BYTES
    /
    max_diff_bytes
    config to override;
    0
    disables).
优先使用
discuss diff
,而非生成git diff的Markdown包装器
。它会完全跳过摘要与代码块的往返流程 —— 二进制文件运行
git diff
,按文件拆分差异,并将每个代码块渲染为
diff-<lang>
块,支持基于行的锚点线程:
discuss diff                  # 暂存区(git diff --cached)
discuss diff --unstaged       # 工作区
discuss diff HEAD~3..HEAD     # 任意范围,直接传递给git diff
discuss plan.md diff          # 同时评审Markdown文件与差异内容
  • session.started
    会新增
    mode
    "markdown"
    /
    "diff"
    /
    "mixed"
    )与
    git_args
    字段,方便你了解评审内容。
  • 每个变更文件都是独立的侧边栏条目,拥有自己的
    fileId
    ;每个文件的说明内容为可选 —— 当需要解释意图时,在文件线程中发布takes;对于机械性变更则保持沉默。
  • 差异输出上限为5 MB(可通过
    --max-diff-bytes
    /
    DISCUSS_MAX_DIFF_BYTES
    /
    max_diff_bytes
    配置项覆盖;设置为
    0
    可禁用上限)。

Verdict options

verdict选项

When the user wants an explicit final review decision (for example, "review this plan, then tell me approved or declined"), pass
--verdict-options
before any
diff
subcommand. Keep it to 2-4 short labels; decline/blocker-style options should usually require feedback with
!
so the transcript explains why.
DSL grammar:
id[:label][:style][!]
separated by
|
.
  • id
    is required and must match
    [a-z0-9_-]+
    ; it becomes
    optionId
    .
  • label
    defaults to the title-cased id.
  • style
    is
    positive
    ,
    neutral
    , or
    negative
    ; default is
    neutral
    .
  • trailing
    !
    makes feedback required for that option.
  • specs need at least 2 options; duplicate ids and case-insensitive duplicate labels are rejected with exit code 2.
discuss --verdict-options 'approved:Approve|declined:Decline:negative!' plan.md
Use
--verdict-prompt "..."
only when the default finish-review prompt needs project-specific wording; without
--verdict-options
it warns on stderr and does nothing. Shell-quote the options because the DSL uses
|
between choices and
!
for required feedback, and both are shell metacharacters.
When
session.done
arrives, read
payload.verdict
if present:
optionId
is the stable choice id,
label
is the displayed button text,
feedback
is the human explanation when supplied, and
decidedAt
is the decision timestamp.
当用户需要明确的最终评审结论时(例如,“评审此计划,然后告诉我通过或拒绝”),在任何
diff
子命令前传递
--verdict-options
参数。选项数量控制在2-4个简短标签;拒绝/阻塞类选项通常需要添加
!
以要求反馈,这样会话记录会解释原因。
DSL语法:
id[:label][:style][!]
,多个选项用
|
分隔。
  • id
    为必填项,必须匹配
    [a-z0-9_-]+
    ;它会成为
    optionId
  • label
    默认将id转换为标题格式。
  • style
    可选值为
    positive
    neutral
    negative
    ;默认值为
    neutral
  • 末尾的
    !
    表示该选项需要必填反馈。
  • 配置至少需要2个选项;重复的id或大小写不敏感的重复标签会被拒绝,退出码为2。
discuss --verdict-options 'approved:Approve|declined:Decline:negative!' plan.md
仅当默认的评审完成提示需要项目特定措辞时,才使用
--verdict-prompt "..."
;如果未指定
--verdict-options
,该参数会在stderr输出警告且不生效。由于DSL使用
|
分隔选项、
!
表示必填反馈,而这两个字符都是shell元字符,因此需要用shell引号包裹选项。
session.done
事件到达时,读取
payload.verdict
(如果存在):
optionId
是稳定的选项id,
label
是按钮显示文本,
feedback
是用户提供的解释(如果有),
decidedAt
是决策时间戳。

Preflight: Ensure
discuss
is installed

预检查:确保已安装
discuss

Run
command -v discuss
(via Bash). If it resolves to a path, skip ahead to Step 0.
If it doesn't resolve, the binary isn't on PATH. Ask the user:
discuss
isn't on your PATH. Install it now? (runs
curl -sSL https://raw.githubusercontent.com/codesoda/discuss-cli/main/install.sh | sh
)
On yes, run the install command via Bash. On completion, retry
command -v discuss
.
If it still doesn't resolve, fall back to the absolute install path:
~/.discuss/bin/discuss
. Check it exists and is executable — if so, use that path for every subsequent call to
discuss
in this session. If it also doesn't exist, report the install failed and stop.
If the user declines the install, stop.
运行
command -v discuss
(通过Bash)。如果返回路径,则跳至步骤0。
如果未返回路径,说明二进制文件不在PATH中。询问用户:
discuss
不在你的PATH中。现在安装它吗?(执行
curl -sSL https://raw.githubusercontent.com/codesoda/discuss-cli/main/install.sh | sh
如果用户同意,通过Bash执行安装命令。安装完成后,重新运行
command -v discuss
如果仍未返回路径,回退到绝对安装路径:
~/.discuss/bin/discuss
。检查该路径是否存在且可执行 —— 如果是,在此会话中后续所有调用
discuss
时都使用该路径。如果该路径也不存在,报告安装失败并停止操作。
如果用户拒绝安装,停止操作。

Step 0: Find the monitor-type tool

步骤0:找到监控类工具

discuss must be launched through a monitor-type tool whenever one exists. A monitor-type tool is any primitive that (a) runs a long-lived command in the background and (b) delivers each stdout line back to you as a notification. That is exactly the contract discuss is built for: the process stays up for the whole review, and every newline-delimited JSON event it prints wakes you with the user's latest thread or reply. No polling, no log scraping, no blocked turn.
Harnesses name these tools differently. Look for whichever pair exists in the current context:
HarnessLaunchStop
Claude Code
Monitor
TaskStop
pi
monitor_start
monitor_stop
otherany "run in background + stream stdout to me" toolits stop/kill call
In Claude Code,
Monitor
and
TaskStop
may be deferred tools. Load their schemas before calling them:
ToolSearch(query: "select:Monitor,TaskStop", max_results: 2)
只要存在监控类工具,就必须通过它启动discuss。监控类工具是满足以下条件的工具:(a) 在后台运行长命令;(b) 将每个stdout行作为通知返回给你。这正是discuss的设计契约:进程会在整个评审期间保持运行,它输出的每个换行分隔的JSON事件都会提醒你用户的最新线程或回复。无需轮询、无需日志抓取、无需阻塞。
不同的执行环境对这些工具的命名不同。查找当前环境中存在的工具对:
执行环境启动命令停止命令
Claude Code
Monitor
TaskStop
pi
monitor_start
monitor_stop
其他环境任何“后台运行 + 将stdout流式传输给我”的工具对应的停止/终止命令
在Claude Code中,
Monitor
TaskStop
可能是延迟加载的工具。调用前先加载它们的 schema:
ToolSearch(query: "select:Monitor,TaskStop", max_results: 2)

Step 1: Launch discuss and choose an event strategy

步骤1:启动discuss并选择事件策略

Always launch through the monitor-type tool first. Only if no such tool is available in the current context (e.g. ToolSearch finds nothing and invoking it returns a tool-not-enabled error) fall back to the polling fallback described below. Do not use the poller when a monitor-type tool exists — it delivers events push-style with no polling latency. The rest of the steps are the same once you have events flowing.
始终优先通过监控类工具启动。只有当当前环境中没有此类工具时(例如,ToolSearch未找到任何工具,或调用时返回工具未启用错误),才回退到下面描述的轮询方案。当存在监控类工具时,请勿使用轮询器 —— 监控类工具采用推送式交付事件,无轮询延迟。一旦事件开始流动,后续步骤都是相同的。

Option A — monitor-type tool (preferred)

选项A —— 监控类工具(优先选择)

Run
discuss
directly as the monitor's command. Two things NOT to do:
  • Do NOT launch it with a plain blocking Bash call — discuss is a server that runs until the user finishes the review, so the call would hold your turn hostage for the entire session.
  • Do NOT launch it via Bash with
    run_in_background
    — you would then have to poll a log file for events, which is the thing the monitor exists to avoid.
The monitor treats each stdout line from its command as an event notification delivered to chat, which is exactly how discuss's newline-delimited JSON events are meant to be consumed.
Never pass
--no-open
— the browser must open by default; the human reviews there. If a session seems to have started silently, check the flags before assuming a server problem.
The command string must start with
discuss
— commands beginning with
discuss
are pre-approved and start immediately; any prefix (
cd … && discuss
,
VAR=x discuss
,
git … | discuss
) requires human approval before the monitor can start. Never prefix with
cd
: the monitor already runs in the session's working directory, so launch from the right cwd and pass repo-relative or absolute paths instead. When piping content in, prefer the heredoc form (
discuss - <<'EOF' … EOF
, which starts with
discuss
) over an upstream-command pipe.
File mode (Claude Code):
Monitor(
  description: "discuss events for <file>",
  command: "discuss \"$ARGUMENTS\"",
  persistent: true
)
The same launch in pi — same command string, same
persistent
, plus an
instruction
that rides along with every wake-up:
monitor_start(
  description: "discuss events for <file>",
  command: "discuss \"$ARGUMENTS\"",
  persistent: true,
  instruction: "Each line is a discuss event. Post a take on thread.created and a follow-up take on reply.added, per the discuss skill."
)
Stdin mode — pipe the markdown content into
discuss -
. Use a heredoc to keep the content readable in the monitor command:
Monitor(
  description: "discuss events for staged-diff review",
  command: "discuss - <<'DISCUSS_EOF'\n# Staged Diff Review\n\n## src/foo.rs\n\n... markdown body ...\nDISCUSS_EOF",
  persistent: true
)
Avoid piping another command's output in (
git diff … | discuss -
) — the command no longer starts with
discuss
and needs human approval. For diffs use
discuss diff
(starts with
discuss
, pre-approved); otherwise capture the content first and use the heredoc form.
Notes:
  • persistent: true
    is required — discuss is a long-running server that only exits when the user is done. Without it the monitor will time out mid-review and take discuss down with it.
  • Do NOT redirect stderr. Monitor-type tools keep stderr out of the event stream (Claude Code writes it to the task output file, pi to a temp log), so discuss's
    listening on …
    stderr line can't pollute the JSON events — but
    2>&1
    would fold it in.
  • Record the id returned by the launch call (
    task_id
    from Monitor, monitor id from
    monitor_start
    ) — you need it to stop the session later.
  • If the port is already bound or the file doesn't exist, discuss exits immediately and the monitor ends without ever emitting a
    session.started
    event. Read the monitor's stderr log to surface the error, then stop.
  • In stdin mode, you typically already have the markdown in hand (you generated it). Keep a copy in your scratchpad if you need it later for anchor snippets — there's no file to re-read.
直接将
discuss
作为监控工具的命令运行。请勿执行以下两件事:
  • 请勿使用普通的阻塞式Bash调用启动它 —— discuss是一个服务器,会运行到用户完成评审,因此该调用会在整个会话期间占用你的执行权限。
  • 请勿通过Bash的
    run_in_background
    启动它 —— 这样你就必须轮询日志文件获取事件,而这正是监控工具要避免的情况。
监控工具会将其命令的每个stdout行作为事件通知发送到聊天窗口,这正是discuss的换行分隔JSON事件的预期消费方式。
永远不要传递
--no-open
参数
—— 浏览器必须默认打开;人类用户在浏览器中进行评审。如果会话似乎静默启动,请先检查标志,再假设服务器出现问题。
命令字符串必须以
discuss
开头
—— 以
discuss
开头的命令是预批准的,可立即启动;任何前缀(如
cd … && discuss
VAR=x discuss
git … | discuss
)都需要人工批准后监控工具才能启动。永远不要添加
cd
前缀:监控工具已经在会话的工作目录中运行,因此请从正确的cwd启动,并传递仓库相对路径或绝对路径。当通过管道传入内容时,优先使用 heredoc 形式(
discuss - <<'EOF' … EOF
,以
discuss
开头),而非上游命令管道。
文件模式(Claude Code):
Monitor(
  description: "discuss events for <file>",
  command: "discuss \"$ARGUMENTS\"",
  persistent: true
)
在pi环境中的启动方式相同 —— 命令字符串相同,
persistent
参数相同,另外添加
instruction
参数,该参数会随每次唤醒传递:
monitor_start(
  description: "discuss events for <file>",
  command: "discuss \"$ARGUMENTS\"",
  persistent: true,
  instruction: "Each line is a discuss event. Post a take on thread.created and a follow-up take on reply.added, per the discuss skill."
)
stdin模式 —— 将Markdown内容通过管道传入
discuss -
。使用heredoc形式使监控命令中的内容更易读:
Monitor(
  description: "discuss events for staged-diff review",
  command: "discuss - <<'DISCUSS_EOF'\n# Staged Diff Review\n\n## src/foo.rs\n\n... markdown body ...\nDISCUSS_EOF",
  persistent: true
)
避免通过其他命令的输出管道传入(如
git diff … | discuss -
)—— 命令不再以
discuss
开头,需要人工批准。对于差异内容,使用
discuss diff
(以
discuss
开头,预批准);否则先捕获内容,再使用heredoc形式。
注意事项:
  • persistent: true
    是必填项 —— discuss是一个长运行服务器,仅在用户完成评审后才会退出。如果没有该参数,监控工具会在评审中途超时,并终止discuss进程。
  • 请勿重定向stderr。监控工具会将stderr与事件流分离(Claude Code将其写入任务输出文件,pi写入临时日志),因此discuss的
    listening on …
    stderr行不会污染JSON事件 —— 但
    2>&1
    会将其合并到事件流中。
  • 记录启动调用返回的id(Monitor返回的
    task_id
    monitor_start
    返回的monitor id)—— 后续停止会话时需要使用该id。
  • 如果端口已被占用或文件不存在,discuss会立即退出,监控工具不会发出
    session.started
    事件。读取监控工具的stderr日志以显示错误,然后停止操作。
  • 在stdin模式下,你通常已经持有Markdown内容(你生成的)。如果后续需要锚点片段,请在临时存储中保留一份副本 —— 因为没有文件可供重新读取。

Option B — Polling fallback (only when no monitor-type tool is available)

选项B —— 轮询回退方案(仅当无监控类工具时使用)

Use this only when no monitor-type background tool is enabled in the current context. If one is available under any name, use Option A.
1. Start discuss in the background:
bash
discuss "$ARGUMENTS" --port <port> > /tmp/discuss-startup.log 2>&1 &
sleep 2
curl -s http://127.0.0.1:<port>/api/state | jq -e 'has("threads")' > /dev/null \
  || { cat /tmp/discuss-startup.log; exit 1; }
Pick a free port by checking which of 7777–7782 isn't already bound (
curl -s http://127.0.0.1:<port>/api/state
). If all are in use, discuss is already running — attach to the existing one.
2. Enter the event loop — blocking poller:
This skill's directory (the directory containing this SKILL.md) also contains
poller.sh
. Call it via Bash (blocking, timeout 600000ms). It polls
/api/state
every 5 seconds and exits as soon as something changes:
bash
bash <skill-dir>/poller.sh "http://127.0.0.1:<port>"
On the first invocation, pass no baseline — the poller snapshots current state itself. On every subsequent invocation, pass the baseline captured from the previous run's
snapshot
line (see below).
  • Exit 0 → one or more new events; parse stdout (one JSON object per line), handle each, then immediately re-invoke the poller with the new baseline.
  • Exit 1 → error (API unreachable); report to user and stop.
  • Exit 2 → session ended (discuss exited); summarize threads and stop.
  • Bash tool timeout → not an error; the session is just quiet. Re-invoke the poller with the same baseline.
3. Handling events from the poller:
On exit 0, stdout contains one line per changed thread, followed by a final
snapshot
line:
json
{"event": "thread.created", "thread": { ...full thread object... }}
{"event": "thread.updated", "thread": { ...full thread object... }, "prev_count": 1, "current_count": 2}
{"event": "snapshot", "baseline": {"<thread-id>": 2, "<thread-id>": 0}}
Handle every
thread.created
and
thread.updated
line exactly as you would
thread.created
and
reply.added
monitor events (see Step 3). On exit 2 the last line is
{"event": "session.done"}
— treat it as the signal to stop and summarize.
Baseline handling: always pass the
baseline
object from the
snapshot
line to the next poller invocation — do NOT re-fetch state to rebuild it yourself, or events that arrive in between will be silently dropped. If you post a reply or take while handling an event, bump that thread's count in the baseline first so your own post doesn't re-fire:
bash
BASELINE=$(echo "$BASELINE" | jq -c --arg id "$THREAD_ID" '.[$id] += 1')
Optionally
Read
the markdown source afterward for context on anchor snippets (file mode only).
仅当当前环境中未启用监控类后台工具时,才使用此方案。如果存在任何名称的监控类工具,请使用选项A。
1. 在后台启动discuss:
bash
discuss "$ARGUMENTS" --port <port> > /tmp/discuss-startup.log 2>&1 &
sleep 2
curl -s http://127.0.0.1:<port>/api/state | jq -e 'has("threads")' > /dev/null \
  || { cat /tmp/discuss-startup.log; exit 1; }
通过检查7777–7782中的哪个端口未被占用(
curl -s http://127.0.0.1:<port>/api/state
),选择一个空闲端口。如果所有端口都被占用,说明discuss已经在运行 —— 连接到现有实例。
2. 进入事件循环 —— 阻塞式轮询器:
此技能的目录(包含此SKILL.md的目录)中还包含
poller.sh
。通过Bash调用它(阻塞式,超时时间600000ms)。它会每5秒轮询一次
/api/state
,一旦有变化就立即退出:
bash
bash <skill-dir>/poller.sh "http://127.0.0.1:<port>"
第一次调用时,无需传递基线 —— 轮询器会自行快照当前状态。后续每次调用时,传递上一次运行的
snapshot
行中捕获的基线(见下文)。
  • 退出码0 → 有一个或多个新事件;解析stdout(每行一个JSON对象),处理每个事件,然后立即使用新基线重新调用轮询器
  • 退出码1 → 错误(API不可达);向用户报告错误并停止操作。
  • 退出码2 → 会话结束(discuss已退出);总结线程并停止操作。
  • Bash工具超时 → 不是错误;会话只是暂时无活动。使用相同基线重新调用轮询器。
3. 处理轮询器的事件:
当退出码为0时,stdout包含每行一个变更线程,最后一行是
snapshot
行:
json
{"event": "thread.created", "thread": { ...完整线程对象... }}
{"event": "thread.updated", "thread": { ...完整线程对象... }, "prev_count": 1, "current_count": 2}
{"event": "snapshot", "baseline": {"<thread-id>": 2, "<thread-id>": 0}}
处理每个
thread.created
thread.updated
行,方式与处理监控事件中的
thread.created
reply.added
完全相同(见步骤3)。当退出码为2时,最后一行是
{"event": "session.done"}
—— 将其视为停止并总结的信号。
基线处理: 始终将
snapshot
行中的
baseline
对象传递给下一次轮询器调用 —— 请勿自行重新获取状态以重建基线,否则期间到达的事件会被静默丢弃。如果在处理事件时发布回复或take,请先在基线中增加该线程的计数,这样你自己的发布不会触发重复事件:
bash
BASELINE=$(echo "$BASELINE" | jq -c --arg id "$THREAD_ID" '.[$id] += 1')
可选:之后
Read
Markdown源文件以获取锚点片段的上下文(仅文件模式)。

Step 2: Confirm startup and capture URL

步骤2:确认启动并捕获URL

The first notification from the monitor should be a
session.started
event:
json
{"kind":"session.started","at":"...","payload":{"url":"http://127.0.0.1:<port>","source_file":"...","started_at":"..."}}
Parse
url
from the payload — use this URL for every subsequent API call. The port is configurable (
--port
, config file), so don't hardcode
7777
.
If the monitor ends without emitting
session.started
, discuss failed to start. Read its stderr log for the error, report it, and stop.
Post a short message to chat:
Session open at
<url>
— watching for threads. Anchor a comment on any part of the doc and I'll post a take.
监控工具的第一个通知应该是
session.started
事件:
json
{"kind":"session.started","at":"...","payload":{"url":"http://127.0.0.1:<port>","source_file":"...","started_at":"..."}}
从负载中解析
url
—— 后续所有API调用都使用此URL。端口是可配置的(
--port
、配置文件),因此不要硬编码
7777
如果监控工具未发出
session.started
就结束,说明discuss启动失败。读取其stderr日志获取错误信息,报告给用户并停止操作。
在聊天窗口中发布一条简短消息:
会话已在
<url>
打开 —— 正在监控讨论线程。在文档的任意部分添加评论锚点,我会发布take。

Step 3: Event loop

步骤3:事件循环

Notifications arrive on the monitor's own schedule — you don't poll. Each notification line is one JSON event. Takes and drafts are broadcast via SSE only (not stdout), so your own
/takes
writes never echo back — no self-echo tracking needed.
Actionable events:
thread.created
,
reply.added
,
thread.resolved
,
thread.deleted
. Lifecycle events (
session.started
,
session.done
,
thread.unresolved
,
prompt.suggest_done
) are informational — acknowledge in chat if useful but don't post to the API.
通知会按监控工具自身的计划到达 —— 你无需轮询。每个通知行都是一个JSON事件。Takes和草稿仅通过SSE广播(而非stdout),因此你自己的
/takes
写入永远不会回显 —— 无需跟踪自回显。
可操作事件:
thread.created
reply.added
thread.resolved
thread.deleted
。生命周期事件(
session.started
session.done
thread.unresolved
prompt.suggest_done
)仅用于提供信息 —— 如果有用,可在聊天窗口中确认,但不要发布到API。

thread.created
(new thread opened by the user)

thread.created
(用户开启新线程)

  1. Read
    anchorStart
    ,
    anchorEnd
    ,
    snippet
    ,
    text
    from the payload.
  2. Locate the anchored region in the markdown source — the
    snippet
    is a reliable search key for the rendered paragraph.
  3. Read the user's comment in
    text
    .
  4. Form a substantive take — answer the question, critique the anchored text, or add the missing piece. Be specific. Reference the anchored content, not just the question in isolation.
  5. Post it as a take, not a reply (substitute the URL from
    session.started
    ):
bash
curl -s -X POST "$URL/api/threads/<thread-id>/takes" \
  -H 'Content-Type: application/json' \
  -d '{"text":"..."}'
  1. 从负载中读取
    anchorStart
    anchorEnd
    snippet
    text
  2. 在Markdown源文件中定位锚定区域 ——
    snippet
    是渲染段落的可靠搜索关键字。
  3. 读取用户在
    text
    中的评论。
  4. 形成实质性的take —— 回答问题、评论锚定文本,或补充缺失的内容。内容要具体。引用锚定内容,而非孤立地回答问题。
  5. 将其作为take发布,而非回复(替换
    session.started
    中的URL):
bash
curl -s -X POST "$URL/api/threads/<thread-id>/takes" \
  -H 'Content-Type: application/json' \
  -d '{"text":"..."}'

reply.added
(the user replied in a thread)

reply.added
(用户在线程中回复)

Replies come only from the human (the API uses
/replies
for humans,
/takes
for you). Any
reply.added
event is a new user message.
  1. Fetch full state:
    curl -s "$URL/api/state"
    — parse the thread and all its replies/takes in order.
  2. Read the latest reply in context.
  3. Decide: is this a question, a challenge, or a genuine opening for more commentary? If yes, post a follow-up take. If it's closure ("thanks", "got it", "makes sense"), stay silent.
  4. If responding, POST another take to the same thread.
回复仅来自人类(API使用
/replies
供人类使用,
/takes
供你使用)。任何
reply.added
事件都是新的用户消息。
  1. 获取完整状态:
    curl -s "$URL/api/state"
    —— 按顺序解析线程及其所有回复/takes。
  2. 在上下文中读取最新回复。
  3. 判断:这是问题、挑战,还是需要更多评论的真正契机?如果是,发布后续take。如果是结束语(“谢谢”、“明白了”、“有道理”),保持沉默。
  4. 如果需要响应,向同一线程发布另一个take。

thread.resolved
/
thread.deleted

thread.resolved
/
thread.deleted

Acknowledge in chat ("
u-3
resolved" / "
u-2
deleted") but do not post anything to the thread.
在聊天窗口中确认(“
u-3
已解决” / “
u-2
已删除”),但不要向线程发布任何内容。

Step 4: Stop conditions

步骤4:停止条件

End the session and shut down when any of these happen:
  • The user types "stop", "end session", "kill it", or similar in chat.
  • The monitored task exits on its own (user quit the browser, server crashed,
    session.done
    event arrived). No further notifications will arrive.
  • The user starts a new unrelated task — don't linger.
On stop:
  1. Stop the monitored task so discuss shuts down with it —
    TaskStop(task_id: <id>)
    in Claude Code,
    monitor_stop(id: <id>)
    in pi.
  2. Summarize: each thread, a one-line takeaway, resolution state.
当发生以下任何情况时,结束会话并关闭:
  • 用户在聊天窗口中输入“stop”、“end session”、“kill it”或类似内容。
  • 监控的任务自行退出(用户关闭浏览器、服务器崩溃、
    session.done
    事件到达)。不会再收到通知。
  • 用户开始新的无关任务 —— 不要停留。
停止时:
  1. 停止监控任务,使discuss随之关闭 —— 在Claude Code中使用
    TaskStop(task_id: <id>)
    ,在pi中使用
    monitor_stop(id: <id>)
  2. 总结:每个线程、一行要点、解决状态。

API reference

API参考

All endpoints at the
url
from
session.started
. Request/response is JSON.
MethodPathBodyPurpose
GET
/api/state
Full snapshot: threads, replies, takes, drafts, verdictConfig
GET
/api/events
SSE stream (alternative to stdout)
POST
/api/threads
{fileId?, anchorStart, anchorEnd, snippet, text}
Create a thread. Rare — usually the user does this.
fileId
required with multiple files.
DELETE
/api/threads/{id}
Soft delete (
kind="user"
only; prepopulated returns 403)
POST
/api/threads/{id}/replies
{text}
Human reply. Do NOT use as the agent.
POST
/api/threads/{id}/takes
{text}
Agent take. This is your primary tool.
POST
/api/threads/{id}/resolve
{decision?}
Resolve a thread
POST
/api/threads/{id}/unresolve
Unresolve
POST
/api/source
{markdown, fileId?, threadAnchors}
Live source update with re-anchoring (see below)
POST
/api/done
{verdict: {optionId, feedback?}}
when verdict options are configured; otherwise optional/ignored
Finish the review. With verdict options, missing body is
400 bad_request
; unknown
optionId
or missing required feedback is
400 validation_error
.
所有端点都位于
session.started
返回的
url
下。请求/响应均为JSON格式。
方法路径请求体用途
GET
/api/state
完整快照:线程、回复、takes、草稿、verdictConfig
GET
/api/events
SSE流(stdout的替代方案)
POST
/api/threads
{fileId?, anchorStart, anchorEnd, snippet, text}
创建线程。很少使用 —— 通常由用户创建。多文件模式下
fileId
为必填项。
DELETE
/api/threads/{id}
软删除(仅
kind="user"
的线程;预填充线程返回403)
POST
/api/threads/{id}/replies
{text}
人类回复。Agent请勿使用。
POST
/api/threads/{id}/takes
{text}
Agent take。这是你的主要工具。
POST
/api/threads/{id}/resolve
{decision?}
解决线程
POST
/api/threads/{id}/unresolve
取消解决
POST
/api/source
{markdown, fileId?, threadAnchors}
实时源更新与重新锚定(见下文)
POST
/api/done
配置verdict选项时为
{verdict: {optionId, feedback?}}
;否则为可选/忽略
完成评审。配置verdict选项时,缺失请求体返回
400 bad_request
;未知
optionId
或缺失必填反馈返回
400 validation_error

Live source updates (
POST /api/source
)

实时源更新(
POST /api/source

If you regenerate the markdown mid-session (e.g. the user fixed code under review and you rebuilt the diff summary), push the new source into the running session instead of restarting it. You own the re-anchor decision: send the full new markdown plus one entry per active thread on that file — either its new anchor position or
"orphaned": true
if its content no longer exists. Coverage is strict and scoped per file; the request is rejected (and nothing changes) if any of that file's active threads is missing, or if you reference a thread from another file. In multi-file sessions pass
fileId
; single-file sessions default to the only file.
json
{
  "markdown": "...entire new document...",
  "threadAnchors": [
    { "threadId": "u-1", "anchorStart": 4, "anchorEnd": 4, "snippet": "optional refreshed snippet" },
    { "threadId": "u-2", "orphaned": true }
  ]
}
Anchors are 1-based indices of commentable block elements (headings, paragraphs, list items, code blocks) in document order — the same units as
anchorStart
on
thread.created
. On success the server re-renders, bumps
sourceVersion
(visible in
/api/state
), and broadcasts
source.updated
on SSE and stdout; the browser swaps the document in place and keeps every conversation. Orphaned threads stay visible to the user, flagged as orphaned. You may pass
sourceVersion
when creating threads via
POST /api/threads
to get a
409 stale_source_version
instead of anchoring against a document that changed under you.
如果你在会话中途重新生成Markdown(例如,用户修复了评审中的代码,你重新构建了差异摘要),请将新源推送到运行中的会话,而非重启会话。你负责重新锚定决策:发送完整的新Markdown,以及每个活跃线程的条目 —— 要么是新的锚点位置,要么是
"orphaned": true
(如果其内容已不存在)。覆盖范围严格按文件划分;如果该文件的任何活跃线程缺失,或引用了其他文件的线程,请求会被拒绝(且不会有任何变更)。多文件会话中需传递
fileId
;单文件会话默认为唯一文件。
json
{
  "markdown": "...完整的新文档...",
  "threadAnchors": [
    { "threadId": "u-1", "anchorStart": 4, "anchorEnd": 4, "snippet": "可选的刷新片段" },
    { "threadId": "u-2", "orphaned": true }
  ]
}
锚点是文档中可添加评论的块元素(标题、段落、列表项、代码块)的1-based索引,与
thread.created
中的
anchorStart
单位相同。成功后,服务器会重新渲染,递增
sourceVersion
(在
/api/state
中可见),并在SSE和stdout上广播
source.updated
;浏览器会替换文档并保留所有对话。孤立线程对用户仍然可见,会标记为孤立。你在通过
POST /api/threads
创建线程时可传递
sourceVersion
,这样如果文档在你创建线程时已变更,会返回
409 stale_source_version
错误,而非锚定到已变更的文档。

Stdout event kinds

stdout事件类型

  • session.started
    {url, mode, source_file, files_count, started_at, git_args?}
  • session.done
    → final transcript payload with optional
    verdict: {optionId, label, feedback?, decidedAt}
  • thread.created
    {id, fileId, kind, anchorStart, anchorEnd, snippet, text, breadcrumb, createdAt}
  • thread.resolved
    {threadId, resolution: {decision, resolvedAt}}
  • thread.unresolved
    {threadId}
  • thread.deleted
    {threadId}
  • reply.added
    {id, threadId, text, createdAt}
    — human reply
  • source.updated
    {markdown, fileId, renderedHtml, threadAnchors, orphanedThreadIds, sourceVersion}
    — a live source update was applied (echo of your own
    POST /api/source
    , or another agent's)
  • prompt.suggest_done
    → lifecycle; informational
Not on stdout:
take.added
,
draft.updated
,
draft.cleared
— these are SSE-only (browser UI), so they never surface here.
  • session.started
    {url, mode, source_file, files_count, started_at, git_args?}
  • session.done
    → 最终会话负载,可选包含
    verdict: {optionId, label, feedback?, decidedAt}
  • thread.created
    {id, fileId, kind, anchorStart, anchorEnd, snippet, text, breadcrumb, createdAt}
  • thread.resolved
    {threadId, resolution: {decision, resolvedAt}}
  • thread.unresolved
    {threadId}
  • thread.deleted
    {threadId}
  • reply.added
    {id, threadId, text, createdAt}
    —— 人类回复
  • source.updated
    {markdown, fileId, renderedHtml, threadAnchors, orphanedThreadIds, sourceVersion}
    —— 已应用实时源更新(你自己的
    POST /api/source
    或其他Agent的更新的回显)
  • prompt.suggest_done
    → 生命周期事件;仅用于提供信息
不在stdout中的事件:
take.added
draft.updated
draft.cleared
—— 这些仅通过SSE传递(浏览器UI),因此永远不会在此处显示。

Authoring markdown for syntax highlighting

为语法高亮编写Markdown

When you generate the markdown to review (especially in stdin mode), tag every code fence with a language so the browser can highlight it. Untagged fences render as plain text.
Common languages:
rust
,
typescript
,
tsx
,
jsx
,
javascript
,
python
,
go
,
java
,
c
,
cpp
,
csharp
,
ruby
,
php
,
swift
,
kotlin
,
bash
,
shell
,
json
,
toml
,
yaml
,
markdown
,
html
,
css
,
scss
,
sql
,
hcl
,
dockerfile
,
nginx
,
ini
,
xml
,
regex
,
graphql
.
Diffs: use
diff
for plain diffs, or
diff-<language>
(e.g.
diff-rust
,
diff-typescript
) for language-aware highlighting on top of the +/- gutter.
Anything else: Prism supports ~300 languages. If you need one not listed above, check prismjs.com/#supported-languages — discuss loads grammars on demand. The list above is curated; the website is authoritative and may include languages added after this skill was written.
当你生成要评审的Markdown时(尤其是在stdin模式下),为每个代码块标记语言,以便浏览器进行语法高亮。未标记的代码块会渲染为纯文本。
常见语言:
rust
typescript
tsx
jsx
javascript
python
go
java
c
cpp
csharp
ruby
php
swift
kotlin
bash
shell
json
toml
yaml
markdown
html
css
scss
sql
hcl
dockerfile
nginx
ini
xml
regex
graphql
差异内容: 普通差异使用
diff
,语言感知的差异使用
diff-<language>
(例如,
diff-rust
diff-typescript
),在+/- gutter之上添加语言高亮。
其他语言: Prism支持约300种语言。如果需要上述未列出的语言,请查看prismjs.com/#supported-languages —— discuss会按需加载语法。上述列表是精选的;网站是权威来源,可能包含此技能编写后新增的语言。

Tone for takes

Takes的语气

  • Be specific to the anchored content, not generic.
  • Push back when you disagree; don't flatter.
  • Cite the source doc when relevant ("line 24 says X, but...").
  • Short is better than long — one or two focused paragraphs beats an essay.
  • If you don't know, say so. Don't speculate.
  • 内容要针对锚定内容,而非泛泛而谈。
  • 不同意时要提出异议;不要奉承。
  • 相关时引用源文档(“第24行说X,但……”)。
  • 简短为佳 —— 一两个重点段落胜过长篇大论。
  • 如果不知道,直接说明。不要猜测。