pi: Pensieve pi 适配层(清理 auto-sediment)#49
Conversation
本扩展的基础设施: - .src/core/hooks.json: 声明式 hook 清单(SessionStart / PreToolUse / PostToolUse / Stop,与 settings.json 兼容格式) - .src/scripts/register-hooks.sh: 幂等注册脚本,合并到用户 settings.json,只触碰带 $PENSIEVE_SKILL_ROOT 或 run-hook.sh 标记的 Pensieve hook,保留其他 hook 不动 - .src/scripts/planning-prehook.sh: PreToolUse/Skill hook 检测 plan-*/autoplan 等规划 skill,注入 .pensieve/pipelines/run-when-planning.md 里的知识检索指令 - .src/scripts/pensieve-session-marker.sh: SessionStart hook 增强, 注入 graph 内容 + marker 版本一致性检查 - .src/scripts/run-hook.sh: 统一 hook wrapper,捕获 → hook-trace.log - .src/scripts/init-project-data.sh: 增加 register-hooks.sh 调用 - .src/templates/pipeline.run-when-*.md: 三个规划/提交/审查 pipeline 模板,含规划前检索、重复检查、diff 分级等知识复利机制 不包含 Stop hook auto-sediment(参见下一个 commit)。 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
每轮评估沉淀的 Stop hook。
- .src/scripts/stop-hook-auto-sediment.sh: per-turn Stop hook,
Filter 0/1/0.5/2/3 链式短路,所有 filter 过 → decision:block 请
求主 Claude 执行 /pensieve self-improve。
Filter 0 (recursion guard): 检查 stop_hook_active != true
Filter 1 (pensieve project): 项目有 .pensieve/
Filter 0.5 (config toggle): .pensieve/config.json auto_sediment.enabled
!= false。配置缺失/字段缺失/malformed/null 均 fallback 到启用
(向后兼容)。热加载,改 config 立即生效,无需重启 Claude Code。
Filter 2 (Ralph-Loop): loop-state.local.md 无 active=true
Filter 3 (message substantial): last assistant message ≥ 200 chars
- .src/core/hooks.json: 注册 Stop hook 到 stop-hook-auto-sediment.sh
- .src/manifest.json: 版本号 bump
设计注意:
- jq 陷阱: ".x.y // true" 在 .x.y 为 false 时会错误返回 true(jq //
对 null/false/empty 三者都触发 default)。使用显式
"if .auto_sediment.enabled == false then yes else no end" 比较。
- 无 git_clean filter: commit pipeline 无自动触发机制,依赖它会永久
丢失 turn 的洞察。
- 无 cooldown / session counter: 任何 turn 可能是最后一个,时间/次数
throttling 会漏掉洞察。
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
docs/extensions-design.md —— 本扩展设计意图完整文档: - §0 摘要 + §1 问题陈述(已有能力 vs 知识流失场景) - §2-3 设计原则 + 总体架构(两条沉淀路径独立并列:用户显式调用 pipeline + 事件驱动 auto-sediment hook) - §4 三层 hook 扩展实现细节(规划前检索 / 会话启动图谱注入 / auto-sediment) - §5 测试与验证 + 三次 verify-before-sediment 应用的失败教训 - §6 与现有 Pensieve 设计的接触面 + merge 策略 - §7 使用说明 + §8 引用 + §9 一句话总结 2026-04-11 注: dispatch mode 在真实生产路径下稳定阻塞失败,已回滚 到 inline 模式 + config.json 开关。文档保留 dispatch 相关段落作为 历史设计档案,顶部加醒目废弃标注。完整回滚背景见项目级 decisions/2026-04-11-sidecar-sediment-dispatch-design.md。 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
新增 Filter 4 — 检测本轮 assistant 输出末尾是否像 Claude 在向用户
提问并等待输入,若是则 silent exit 0 跳过 sediment 评估。
## 动机
Claude 在对话中需要用户决策时,常选文字提问 + 结束 turn 的模式
而不是调用 AskUserQuestion 工具。文字提问 turn 会触发 Stop hook
→ auto-sediment Filter 0/1/0.5/2/3 全 PASS → 输出 decision:block
→ 主 Claude 续轮评估 → 大概率 NO_SEDIMENT(提问不是洞察)→ 浪费
600-5000 token/次。高频讨论场景单任务浪费 5-10k token。
## 实现
stop-hook-auto-sediment.sh 在 Filter 3 之后加 Filter 4:
```bash
if [[ "$LAST_MSG_LEN" -gt 300 ]]; then
TAIL_MSG="${LAST_MSG:$(( LAST_MSG_LEN - 300 ))}"
else
TAIL_MSG="$LAST_MSG"
fi
if [[ "$TAIL_MSG" =~ [\??][[:space:]]*$ ]] \
|| [[ "$TAIL_MSG" =~ 要不要[^。]*$ ]] \
|| [[ "$TAIL_MSG" =~ 需要[我你]?[^。]*吗 ]] \
|| [[ "$TAIL_MSG" =~ 是否[^。]*[\??] ]] \
|| [[ "$TAIL_MSG" =~ 你(想|觉得|希望|打算)[^。]*[\??] ]] \
|| [[ "$TAIL_MSG" =~ 哪[一个种][^。]*[\??] ]] \
|| [[ "$TAIL_MSG" =~ 请[选确][^。]*$ ]] \
|| [[ "$TAIL_MSG" =~ [Ww]ould[[:space:]]+you ]] \
|| [[ "$TAIL_MSG" =~ [Ww]hich[[:space:]] ]] \
|| [[ "$TAIL_MSG" =~ [Ss]hould[[:space:]]+[IWwYy] ]]; then
echo "$(date -u +%Y-%m-%dT%H:%M:%SZ) filter4-question-detected" \
>> "$STATE_ROOT/hook-trace.log" 2>/dev/null || true
exit 0
fi
```
取最后 300 字符避免长消息主体影响匹配。不用 pipe 避免 pipefail
陷阱。记录 `filter4-question-detected` 到 hook-trace.log 便于
后期观察启发式质量。
## 启发式精度预期 70-85%
live test 覆盖 4 个场景:
- 英文 "Would you like..." (227 chars):✓ 拦截
- 英文陈述句 "...fixed in commit abc1234." (314 chars):✓ 放行
- 中文陈述句 "已在 commit abc1234 修复。" (246 chars):✓ 放行
- 中文长提问 "...你觉得哪个方案更合适?" (315 chars):✓ 拦截
边界 case 会漏(英文 "Do you..." / "Can you..." / 修辞反问)
或误判(陈述句中间有 "?" 字符)。记录 trace 用于迭代。
## 配套软约束
CLAUDE.md 已添加"提问优先 AskUserQuestion"规则(父项目同批 commit)
作为认知引导。软 + 硬双管齐下:
- 软约束让 Claude 主动改行为
- 硬约束兜底处理 default behavior drift
## 文档更新
docs/extensions-design.md:
- §2.4 过滤器最小化:加 ✅ pending_question 条目 + 表格行
"剩下的 5 个都是必需" → "剩下的 6 个都是必需"
- §4.3 流程图:加 过滤器 4: pending_question 行
- 附录 A 版本历史:加 1.5.1 2026-04-11 条目描述本次改动
.src/manifest.json: 版本 1.5.0 → 1.5.1
## 依据
knowledge/auto-sediment-text-question-stop-waste(本会话沉淀)
解释了文字提问的反模式、成本量化、解决方案矩阵,以及反模式亲历
——作者在讨论本方案的那一轮自己还是用文字问了 4 个问题。
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
stop-hook 在 Filter 3 pass 后记录样本到全局 jsonl 文件,用于离线分析
Filter 4 启发式的 precision(看 blocked 样本是否真是问题)和 recall
(看 sediment-fired 里是否有漏检的隐形提问)。
## 文件与格式
- 路径:~/.claude/.pensieve-filter-samples.jsonl
- 权限:600(仅用户可读,防 secret 泄露)
- Rotation:> 5MB → .1 → .2 → .3,保留 3 代(~20MB 上限)
- 格式:JSONL,每行一条
```jsonl
{"ts":"2026-04-12T00:58:18Z","session":"<uuid>","project":"/path","decision":"sediment-fired","msg_len":314,"tail":"...末尾300字符..."}
{"ts":"2026-04-12T00:58:49Z","session":"<uuid>","project":"/path","decision":"filter4-blocked","msg_len":306,"tail":"..."}
```
decision ∈ {filter4-blocked, sediment-fired}
## 实现
- 从 hook payload 提取 session_id 字段一并记录
- 定义 record_filter_sample() 函数,集中写入逻辑
- Filter 4 命中路径:record_filter_sample "filter4-blocked" → exit 0
- 全 filter pass 路径:record_filter_sample "sediment-fired" → decision:block JSON
- jq 构造 JSONL 行(--arg/--argjson 规范转义)
- stat -c %s 检查文件大小触发 rotation
- chmod 600 确保权限
## 分析路径(未来离线工作)
```bash
# 查 Filter 4 命中样本的 tail(人工审阅 precision)
jq -r 'select(.decision=="filter4-blocked") | .tail' ~/.claude/.pensieve-filter-samples.jsonl
# 查 sediment-fired 里有没有看起来像问题但漏检的(recall 问题)
jq -r 'select(.decision=="sediment-fired") | .tail' ~/.claude/.pensieve-filter-samples.jsonl | grep -E '[??]$|吗$'
# 跨项目分布
jq -r '.project' ~/.claude/.pensieve-filter-samples.jsonl | sort -u
```
## 隐私
tail 可能含代码/internal discussion/意外出现的 secret。**不要上传、不要
分享、不要提交到 git**。文件位于 $HOME 隐藏文件,不在任何 pensieve 项目
git 范围内,但用户可能同步 $HOME(dropbox/rsync),需要明确在文档中提醒。
## 动机
用户在 Filter 4 落地后建议"把 stop hook 的 last_assistant_message
末尾保存到全局文件用于后续丰富启发式问答判断依据"。Filter 4 的正则
是初始猜测版本(70-85% 精度),只有在**真实语料**上分析才能提升:
- 看看 filter4-blocked 里有没有误判的陈述句
- 看看 sediment-fired 里有没有漏检的提问
- 发现新的中英文提问句式模板
## 文档与版本
- docs/extensions-design.md §4.3 流程图加样本日志输出说明
- 附录 A 新增 1.5.2 条目
- .src/manifest.json 1.5.1 → 1.5.2
## Live test
test 1(中文长提问 306 chars):
✓ filter4-blocked 记录到 jsonl
test 2(英文陈述句 314 chars):
✓ sediment-fired 记录到 jsonl
文件权限 600 ✓,JSONL 格式正确 ✓。
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
用户反馈 1.5.2 的样本记录两个问题:
1. Filter 4 blocked 的 turn 是"被正确筛选的",不需要保存
2. 不能用 NO_SEDIMENT 续轮信号判断"这轮是不是提问",需要完整
上下文人工判断
1.5.3 修正:
## 去掉 decision 分类
hook 运行时无法可靠分类样本属于哪种:
- filter4-blocked 里可能有误杀陈述句(但无法自动识别)
- sediment-fired 里可能有漏检的问题(但无法自动识别)
- 续轮 NO_SEDIMENT 不等于"这轮是问题",可能是无洞察、已沉淀、话题
切换等多种原因(用户原话:需要完整上下文判断)
样本 schema 简化为:
{"ts":"...","session":"...","project":"...","msg_len":...,"tail":"..."}
没有 decision / followup_reason 字段。人工离线看 tail 内容判断是不是
应该被 Filter 4 拦的问题 turn,用于调优正则。
## 只记录 Filter 4 放行路径
- Filter 4 blocked → 仅 hook-trace.log 记 filter4-question-detected
(可观察性保留),不写 filter-samples.jsonl
- Filter 4 passed → record_sample() 写样本,然后 decision:block 触发
sediment 评估
## 回退 1.5.2 引入的延迟记录机制
- 去掉 pending sample 文件 + Filter 0 的 deferred recording 逻辑
- Filter 0 恢复简单 recursion guard
- 清理 write_pending_sample() 函数,改回 record_sample() 直接写
## Live test
test 1 英文陈述句 289 chars(Filter 4 passed):
✓ 记录样本 + decision:block JSON 输出
✓ 样本 schema 正确(无 decision 字段)
test 2 长提问 269 chars(Filter 4 blocked):
✓ 不记录样本
✓ hook-trace.log 新增 filter4-question-detected 条目
test 3 recursion guard (stop_hook_active=true):
✓ 不记录 + 无 pending 文件残留
## 文档
docs/extensions-design.md §4.3 流程图简化,附录 A 加 1.5.3 条目
说明本次修正。manifest 1.5.2 → 1.5.3。
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
hookSpecificOutput 缺少 hookEventName + permissionDecision, 导致 Claude Code v2.1.x schema 校验报错。 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
非对称 session 隔离导致死会话的 loop-state (active: true) 永久 静音 auto-sediment。现在 Filter 2 比对 loop session_id 与当前 session,不匹配时视为孤儿放行。 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Non-Claude-Code harnesses (pi, cursor, generic agents) integrate via their own extension API and must not write to ~/.claude/settings.json. Set PENSIEVE_HARNESS=pi (or any non-empty non-"claude-code" value) to skip the register-hooks.sh call at the end of init-project-data.sh. Default behavior is unchanged for Claude Code users. Tested: PENSIEVE_HARNESS=pi init-project-data.sh leaves ~/.claude/settings.json byte-identical (verified by SHA256).
Adds an opt-in adapter so Pensieve runs natively under
mariozechner/pi-coding-agent without forking the core.
Components
----------
* pi/extensions/pensieve-context/ — ~150 LOC TS extension that:
- Detects `<cwd>/.pensieve/` on session_start.
- Appends a small (\~300 token) navigation card to the system prompt
in `before_agent_start`. The card lists the four memory layers,
points at the mermaid graph + state.md, and tells the LLM when to
consult Pensieve and when to call `/skill:pensieve self-improve`.
Detail lookup is delegated to the model — no full-graph dump.
- Bridges pi's `tool_result` event for `edit` / `write` to
`sync-project-skill-graph.sh`, the same script Claude Code's
PostToolUse hook uses, so the graph stays fresh on .pensieve/ edits.
* pi/install.sh — idempotent installer:
- Verifies `~/.pi/agent/skills/pensieve/.src/manifest.json` (skill present).
- Symlinks the extension into `~/.pi/agent/extensions/`.
- On a real project cwd, runs `init-project-data.sh` with
`PENSIEVE_HARNESS=pi` so register-hooks.sh is skipped.
* pi/README.md — adapter design notes and verify steps.
Design
------
* Single source of truth — all real logic lives in `.src/scripts/*.sh`.
The pi side is a thin event router. No bash logic ported to JS.
* Minimum upstream change — only the existing
`PENSIEVE_HARNESS=pi` guard in init-project-data.sh (separate commit).
* Layered priority per user direction: P1 = knowledge graph injection
(this commit), P2 = auto-sediment is future work in
`pi/extensions/pensieve-auto-sediment/`. The skill itself works
without any extension.
Verified
--------
End-to-end on a real pi session (model: anthropic claude-sonnet-4):
1. `pi -p` with `.pensieve/` present — LLM correctly enumerates
layer counts, graph path, and the consult/write protocol from the
system prompt only (no tool calls).
2. Same prompt with `.pensieve/` absent — LLM reports no Pensieve
memory; no card injected (graceful degradation).
3. LLM uses `write` to add a decision file to `.pensieve/decisions/`
— graph file mtime advances and the new entry appears in mermaid.
README.md
---------
Adds a `pi` install `<details>` block alongside Claude Code and the
generic-client block, with submodule and standalone install paths.
Closes the P1 'knowledge graph context injection' goal.
The sync-project-skill-graph.sh case statement only matched top-level maxims/*, decisions/*, knowledge/*, pipelines/* but not short-term/*. Edits to short-term files would silently skip graph regeneration. Now short-term/* files also trigger the graph refresh, consistent with the short-term spec: short-term entries share the same graph node namespace as long-term ones. Verified end-to-end on pi: edit a .../short-term/knowledge/*/content.md → graph mtime advances.
Layer 3 — per-prompt auto-sediment ================================== pi/extensions/pensieve-auto-sediment/index.ts (~130 LOC): - Listens to pi's agent_end event. - Pre-fetches all ctx data synchronously (sessionManager.getSessionFile, getBranch, cwd, hasUI, signal) before any async spawn, avoiding the stale-ctx error that occurs when pi finalizes the session during await. - Constructs Claude-Code-compatible stdin JSON for the upstream stop-hook-auto-sediment.sh filter chain. - On decision:block, in interactive mode (hasUI=true) sends the sediment evaluation prompt as a follow-up user message via sendUserMessage. - In print mode (-p), auto-sediment evaluation is skipped — batch usage doesn't benefit from mid-stream injection. - Recursion is guarded by an in-memory Set<sessionId> (pi has no stop_hook_active field but the Set provides equivalent protection). Also: make the upstream sample log path configurable via PENSIEVE_SAMPLE_LOG env var (default unchanged: ~/.claude/...). pensieve-wand skill =================== pi/skills/pensieve-wand/SKILL.md — pi-native adaptation of the Claude Code pensieve-wand agent. Strips Claude-Code-specific frontmatter (model, color, memory) and adapts the System 1/System 2 decision workflow for pi's context (pensieve-context navigation card = System 1; graph exploration = System 2). Memory persistence uses .pensieve/knowledge/ entries instead of Claude-Code-specific MEMORY.md files. install.sh updated ================== - --with-auto-sediment flag to opt into Layer 3 - Auto-links pensieve-wand skill into ~/.pi/agent/skills/ Verified ======== - pi -p mode: no stale ctx error, agent_end fires, sample log recorded - bash script direct test: filter chain passes on substantive messages - pensieve-wand frontmatter valid: name matches directory, description present
Replaces copy-pasted symlink code with a shared install_extension() function that uses realpath --relative-to to create relative symlinks. Extensions and skills now survive cloning ~/.pi to a different HOME.
…global defaults
Major rework of pensieve-auto-sediment from inline-followup mode to
detached in-process sidecar. Main session sees 0 token cost / 0 latency /
0 transcript pollution; sediment evaluation and writing happen on a
configurable side LLM, off the UI await chain.
Architecture:
agent_end → upstream stop-hook-auto-sediment.sh (filter chain unchanged)
→ if PASS, detached promise:
├─ DECISION llm → JSON {sediment, kind, slug, label}
├─ if sediment:
│ ├─ WRITER llm → markdown (per .src/references/<kind>.md)
│ ├─ write short-term/<kind>/<slug>.md
│ └─ spawn maintain-project-state.sh (detached + unref)
└─ all events → .pensieve/.state/sidecar-sediment.log
Module split:
index.ts — agent_end router; concurrency state machine (latest-wins
single-slot queue); ctx capture before await
config.ts — four-tier resolution (NEW)
prompts.ts — decision + writer system prompts (NEW)
sidecar.ts — detached pipeline; LLM via @mariozechner/pi-ai (NEW)
Configuration — four-tier override (NEW):
env > project .pensieve/config.json > global ~/.pi/.pensieve-auto-sediment.json > ctx.model
Per-key asymmetry is intentional:
- model : full four-tier (CI/repo/machine/fallback all legitimate)
- timeout : project > global > default (no env — wrong ergonomics)
- min_length : env > project > default (no global — per-project policy)
- enabled : project only (cross-harness toggle, also read by bash)
Why pi-only global path (not ~/.config/pensieve/config.json):
CC's auto-sediment runs inline (sidecar-dispatch archived, see CC's
2026-04-11 decision). Putting decision_model/writer_model under a
"global pensieve" path would mislead users into expecting CC honors
them. The pi-specific filename makes scope explicit.
Bug fixes folded in:
- knowledge files were being written to short-term/knowledges/ (plural);
fixed: kind === "knowledge" ? "knowledge" : `${kind}s`. Pensieve dir
naming is asymmetric on purpose (knowledge is singular).
- PENSIEVE_SEDIMENT_MIN_LENGTH env was hard-coded to 200 in the bash
spawn; now mirrors resolved config.minLength so TS pre-filter and bash
Filter 3 use the same threshold.
- loadConfig was called twice per agent_end (once for enabled check,
once inside launchSidecar); now resolved once and threaded through
DispatchInputs.
- maintain-project-state.sh subprocess now `detached: true` + unref()
so pi can exit cleanly in print mode without waiting on state refresh.
Removed:
- pi.sendUserMessage(reason, { deliverAs: "followUp" }) injection path
- sedimentInProgress recursion guard (no longer needed; nothing re-enters)
Default model recommendation (README):
decision_model: anthropic/claude-haiku-4-5
writer_model: anthropic/claude-sonnet-4-6
Validation:
- Smoke test: jiti loads all 4 modules, factory exports OK
- Config tests: 4-layer model precedence, env/project/global combinations,
malformed/missing global file, minLength env ↔ project priority
- Live run after restart produced two correct short-term entries
(pi-only-sidecar-config-path decision + pensieve-config-precedence-
asymmetry knowledge), both written to the correct directories.
Refs:
~/.pi/.pensieve/short-term/decisions/2026-04-30-pi-auto-sediment-inprocess-sidecar.md
~/.pi/.pensieve/short-term/decisions/2026-04-30-pi-only-sidecar-config-path.md
~/.pi/.pensieve/short-term/knowledge/pi-sidecar-is-inprocess-llm-call/
~/.pi/.pensieve/short-term/knowledge/pensieve-config-precedence-asymmetry/
- setStatus('Pensieve 沉淀中...') when sidecar starts
- clear status on completion (unless replaying pending message)
- prevents user from quitting pi before background sediment finishes
…covery Enables pi to auto-discover pensieve extensions (auto-sediment, context) and skills (pensieve-wand) via settings.json paths instead of symlinks.
- Replaces symlink creation with jq-based settings.json path injection - Adds cleanup of legacy symlinks from pre-package.json era - Unified approach with pi-gstack: no symlinks, settings.json-driven discovery - Idempotent: detects existing paths, cleans up old symlinks
- Removed pi/extensions/pensieve-auto-sediment/ (replaced by pi-sediment) - Updated install.sh: removed --no-auto-sediment, default branch → pi - Updated package.json: removed auto-sediment from description
There was a problem hiding this comment.
Pull request overview
This PR adds a pi adapter layer for Pensieve (extension + installer + pi skill), introduces a pi package manifest, and refactors/extends the Claude Code hook-based workflow (planning prehook, hook self-registration, stop-hook auto-sediment), while removing the legacy pi auto-sediment extension path (now intended to live in pi-sediment).
Changes:
- Added
pi/integration:pensieve-contextextension (system prompt “navigation card” + graph sync),pensieve-wandskill, and an idempotentpi/install.shthat configures~/.pi/agent/settings.json. - Added Claude Code hook lifecycle automation (
hooks.json+register-hooks.sh) and new hooks/scripts (planning prehook + stop-hook auto-sediment), plus pipeline template enhancements. - Updated docs/manifests to reflect the new integration surfaces (repo
package.json, root README pi section, manifest version bump).
Reviewed changes
Copilot reviewed 20 out of 20 changed files in this pull request and generated 12 comments.
Show a summary per file
| File | Description |
|---|---|
pi/skills/pensieve-wand/SKILL.md |
Adds the pi-native “pensieve-wand” retrieval skill spec. |
pi/install.sh |
Adds an installer to configure pi via settings.json paths and optionally init project data. |
pi/extensions/pensieve-context/package.json |
Declares the pi extension package entrypoint. |
pi/extensions/pensieve-context/index.ts |
Implements navigation card injection + .pensieve/ edit detection to trigger graph sync. |
pi/README.md |
Documents the pi adapter subtree and installation/verification guidance. |
package.json |
Adds a pi package manifest exposing ./pi/extensions and ./pi/skills. |
docs/extensions-design.md |
Adds a detailed design spec for hooks/auto-sediment/planning retrieval. |
README.md |
Adds a pi-specific installation section and pointers to pi/README.md. |
.src/templates/pipeline.run-when-reviewing-code.md |
Adds diff-size grading and review breadth guidance. |
.src/templates/pipeline.run-when-planning.md |
Adds a planning-time knowledge retrieval pipeline template. |
.src/templates/pipeline.run-when-committing.md |
Expands sediment signals and adds duplicate-check guidance before writing. |
.src/scripts/sync-project-skill-graph.sh |
Extends graph sync coverage to short-term/* edits. |
.src/scripts/stop-hook-auto-sediment.sh |
Adds Stop-hook inline auto-sediment logic with filters + sampling log. |
.src/scripts/run-hook.sh |
Adds hook invocation trace logging to .pensieve/.state/hook-trace.log. |
.src/scripts/register-hooks.sh |
Adds idempotent hook registration into ~/.claude/settings.json from hooks.json. |
.src/scripts/planning-prehook.sh |
Adds a PreToolUse hook that injects planning-related Pensieve context. |
.src/scripts/pensieve-session-marker.sh |
Injects (truncated) knowledge graph into SessionStart context. |
.src/scripts/init-project-data.sh |
Calls hook registration unless running under a non-Claude harness. |
.src/manifest.json |
Bumps skill version to 1.5.3. |
.src/core/hooks.json |
Declares hook configuration as the single source of truth for registration. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| ├── extensions/ | ||
| │ ├── pensieve-context/ ← Layer 2: knowledge graph injection | ||
| │ │ ├── package.json | ||
| │ │ └── index.ts | ||
| │ └── pensieve-auto-sediment/ ← Layer 3: per-prompt auto-sediment | ||
| │ ├── package.json | ||
| │ └── index.ts |
There was a problem hiding this comment.
pi/README.md still documents a pi/extensions/pensieve-auto-sediment/ directory (Layer 3) that is not present in this PR’s tree, so following the README would lead users to a non-existent extension. Update the tree listing and the layer table to reflect the current set of extensions (only pensieve-context) and/or link to the external pi-sediment replacement if that’s the intended path.
| The installer: | ||
| 1. Verifies the skill is present. | ||
| 2. Symlinks `pensieve-context` extension into `~/.pi/agent/extensions/`. | ||
| 3. Symlinks `pensieve-wand` skill into `~/.pi/agent/skills/`. | ||
| 4. If `cwd` is a real project (not the dotfiles repo itself), runs | ||
| `init-project-data.sh` with `PENSIEVE_HARNESS=pi` — that env var skips the | ||
| Claude-Code-only `register-hooks.sh` step that would otherwise touch | ||
| `~/.claude/settings.json`. | ||
|
|
||
| For Layer 3 (auto-sediment), add `--with-auto-sediment`: | ||
|
|
||
| ```bash | ||
| bash agent/skills/pensieve/pi/install.sh --with-auto-sediment | ||
| ``` | ||
|
|
||
| If you already have a Pensieve checkout somewhere else (e.g. `~/.claude/skills/pensieve`), | ||
| point the installer at it: | ||
|
|
||
| ```bash | ||
| PENSIEVE_SKILL_PATH=~/.claude/skills/pensieve bash pi/install.sh --no-init-project | ||
| ``` |
There was a problem hiding this comment.
The install section describes creating symlinks and a --with-auto-sediment flag, but pi/install.sh now writes paths into ~/.pi/agent/settings.json and doesn’t implement --with-auto-sediment. Please update these installer steps/flags so the README matches the actual behavior (otherwise users will run commands that fail or do nothing).
| IS_PLANNING=false | ||
| case "$TOOL_NAME" in | ||
| EnterPlanMode) IS_PLANNING=true ;; | ||
| esac | ||
| case "$SKILL_NAME" in | ||
| plan-*|autoplan|office-hours) IS_PLANNING=true ;; | ||
| esac | ||
|
|
||
| [[ "$IS_PLANNING" == "true" ]] || exit 0 | ||
|
|
||
| # Detect project root and .pensieve/ directory. | ||
| PROJECT_ROOT="$(project_root 2>/dev/null)" || exit 0 | ||
| PENSIEVE_DIR="$PROJECT_ROOT/.pensieve" | ||
| [[ -d "$PENSIEVE_DIR" ]] || exit 0 | ||
|
|
||
| # Read planning pipeline if it exists. | ||
| PLANNING_PIPELINE="$PENSIEVE_DIR/pipelines/run-when-planning.md" | ||
| PIPELINE_CONTENT="" | ||
| if [[ -f "$PLANNING_PIPELINE" ]]; then | ||
| PIPELINE_CONTENT=$(cat "$PLANNING_PIPELINE" 2>/dev/null || true) | ||
| fi | ||
|
|
||
| # Quick grep for decisions with "探索减负" (exploration reduction). | ||
| PRIOR_ART="" | ||
| for dir in "$PENSIEVE_DIR/decisions" "$PENSIEVE_DIR/knowledge" "$PENSIEVE_DIR/maxims"; do | ||
| [[ -d "$dir" ]] || continue | ||
| # Find files with active status | ||
| while IFS= read -r f; do | ||
| [[ -f "$f" ]] || continue | ||
| # Extract first heading and one-line conclusion/summary | ||
| title=$(grep -m1 '^# ' "$f" 2>/dev/null | sed 's/^# //' || true) | ||
| status=$(grep -m1 '^status:' "$f" 2>/dev/null | sed 's/^status:[[:space:]]*//' || true) | ||
| [[ "$status" == "active" ]] || continue | ||
| rel_path="${f#$PENSIEVE_DIR/}" | ||
| PRIOR_ART="${PRIOR_ART}\n- ${rel_path}: ${title}" | ||
| done < <(find "$dir" -name '*.md' -type f 2>/dev/null | LC_ALL=C sort) | ||
| done | ||
|
|
||
| # Build additional context. | ||
| CTX="" | ||
| if [[ -n "$PIPELINE_CONTENT" ]]; then | ||
| CTX="## Planning Pipeline (run-when-planning)\n\n${PIPELINE_CONTENT}" | ||
| fi | ||
| if [[ -n "$PRIOR_ART" ]]; then | ||
| CTX="${CTX}\n\n## Available Pensieve Knowledge\n${PRIOR_ART}" | ||
| fi | ||
|
|
||
| if [[ -z "$CTX" ]]; then | ||
| exit 0 | ||
| fi |
There was a problem hiding this comment.
This script exits early with no output in multiple cases ([[ "$IS_PLANNING" == "true" ]] || exit 0, missing .pensieve/, empty CTX, etc.). In this repo, explore-prehook.sh documents that Claude Code requires valid JSON output from PreToolUse hooks even on no-op paths; empty stdout can be treated as an error. Please return a minimal allow JSON payload on all no-op/fallback paths (and only add additionalContext when applicable).
There was a problem hiding this comment.
These are Claude Code hook scripts maintained for CC compatibility. The issues raised are valid but out of scope for this pi-adapter PR — will address in a separate CC-focused PR.
| # Read hook payload from stdin. | ||
| HOOK_INPUT="" | ||
| if [[ ! -t 0 ]]; then | ||
| HOOK_INPUT=$(timeout 2 cat 2>/dev/null || true) |
There was a problem hiding this comment.
Hook payload reading uses timeout 2 cat ... without checking whether timeout exists. On macOS (common for Claude Code), timeout isn’t available by default, which can emit command not found noise on stderr and may interfere with hook execution. Either avoid timeout here (stdin should be finite) or guard with command -v timeout and fall back to plain cat.
| HOOK_INPUT=$(timeout 2 cat 2>/dev/null || true) | |
| if command -v timeout >/dev/null 2>&1; then | |
| HOOK_INPUT=$(timeout 2 cat 2>/dev/null || true) | |
| else | |
| HOOK_INPUT=$(cat 2>/dev/null || true) | |
| fi |
There was a problem hiding this comment.
These are Claude Code hook scripts maintained for CC compatibility. The issues raised are valid but out of scope for this pi-adapter PR — will address in a separate CC-focused PR.
| "$PYTHON_BIN" -c " | ||
| import json, sys | ||
| ctx = sys.stdin.read() | ||
| payload = { | ||
| 'hookSpecificOutput': { | ||
| 'hookEventName': 'PreToolUse', | ||
| 'permissionDecision': 'allow', | ||
| 'additionalContext': ctx, | ||
| }, | ||
| } | ||
| print(json.dumps(payload, ensure_ascii=False)) | ||
| " <<< "$(echo -e "$CTX")" |
There was a problem hiding this comment.
The Python handoff uses <<< "$(echo -e "$CTX")". echo -e can reinterpret backslash sequences in the pipeline content / titles, which can corrupt the injected context. Prefer a safer transport (e.g., printf '%s' "$CTX" into stdin) that does not perform escape processing.
There was a problem hiding this comment.
These are Claude Code hook scripts maintained for CC compatibility. The issues raised are valid but out of scope for this pi-adapter PR — will address in a separate CC-focused PR.
| # --- Read payload --- | ||
| HOOK_INPUT="" | ||
| if [[ ! -t 0 ]]; then | ||
| HOOK_INPUT=$(timeout 2 cat 2>/dev/null || true) |
There was a problem hiding this comment.
Hook payload reading uses timeout 2 cat without checking if timeout exists. On macOS, this can print command not found to stderr during every Stop hook fire. Consider guarding with command -v timeout or falling back to plain cat to keep the hook silent/robust.
| HOOK_INPUT=$(timeout 2 cat 2>/dev/null || true) | |
| if command -v timeout >/dev/null 2>&1; then | |
| HOOK_INPUT=$(timeout 2 cat 2>/dev/null || true) | |
| else | |
| HOOK_INPUT=$(cat 2>/dev/null || true) | |
| fi |
There was a problem hiding this comment.
These are Claude Code hook scripts maintained for CC compatibility. The issues raised are valid but out of scope for this pi-adapter PR — will address in a separate CC-focused PR.
| ```bash | ||
| # 1. 在 ~/.pi 里加 submodule | ||
| git -C ~/.pi submodule add -b feature/auto-sediment-hook \ | ||
| https://github.com/kingkongshot/Pensieve.git agent/skills/pensieve | ||
|
|
||
| # 2. 跳过 register-hooks.sh(那只对 Claude Code 有意义), | ||
| # 安装 pi-context extension + 初始化项目数据 | ||
| bash ~/.pi/agent/skills/pensieve/pi/install.sh | ||
| ``` | ||
|
|
||
| 如果你不想用 submodule,`install.sh` 也能独立 clone: | ||
|
|
||
| ```bash | ||
| bash <(curl -fsSL https://raw.githubusercontent.com/kingkongshot/Pensieve/feature/auto-sediment-hook/pi/install.sh) | ||
| ``` |
There was a problem hiding this comment.
The pi install instructions hard-code the feature/auto-sediment-hook branch in both the submodule command and the raw.githubusercontent.com curl example. This PR switches the installer default branch to pi, so these commands should be updated (or changed to rely on the repo default) to avoid pointing users at an outdated branch.
| fi | ||
|
|
||
| # ─── 2. Update settings.json ───────────────────────────────────────────────── | ||
| if [[ ! -f "$SETTINGS_FILE" ]]; then |
There was a problem hiding this comment.
settings.json is created via echo "{}" > "$SETTINGS_FILE" but the parent directory ($HOME/.pi/agent/) may not exist on a fresh install, causing the script to fail under set -e. Create $(dirname "$SETTINGS_FILE") before writing the file.
| if [[ ! -f "$SETTINGS_FILE" ]]; then | |
| if [[ ! -f "$SETTINGS_FILE" ]]; then | |
| mkdir -p "$(dirname "$SETTINGS_FILE")" |
| // ───────────────────────────────────────────────────────────────────────────── | ||
| // Locate the Pensieve skill root. | ||
| // Priority: PENSIEVE_SKILL_ROOT env > submodule path under ~/.pi > legacy ~/.claude. | ||
| // ───────────────────────────────────────────────────────────────────────────── | ||
| function locateSkillRoot(): string | null { | ||
| const candidates = [ | ||
| process.env.PENSIEVE_SKILL_ROOT, | ||
| path.join(os.homedir(), ".pi", "agent", "skills", "pensieve"), | ||
| path.join(os.homedir(), ".claude", "skills", "pensieve"), | ||
| ].filter((p): p is string => !!p); | ||
| for (const c of candidates) { | ||
| if (fs.existsSync(path.join(c, ".src", "manifest.json"))) return c; | ||
| } | ||
| return null; | ||
| } |
There was a problem hiding this comment.
locateSkillRoot() only checks $PENSIEVE_SKILL_ROOT, ~/.pi/agent/skills/pensieve, and ~/.claude/skills/pensieve. If the repo is installed elsewhere (which install.sh --skill-path explicitly supports), the extension can still be loaded (via settings.json path) but SKILL_ROOT will be null, disabling graph sync and showing placeholder paths in the navigation card. Consider deriving the skill root from the extension file location (e.g., via import.meta.url) as a reliable fallback.
There was a problem hiding this comment.
Acknowledged. These are valid improvements but the current behavior is safe (graceful degradation). Will address in a follow-up.
| pi.on("tool_result", (event: ToolResultEvent, ctx) => { | ||
| if (!pensieveDir || event.isError) return; | ||
| if (event.toolName !== "edit" && event.toolName !== "write") return; | ||
| const raw = (event.input as { path?: string }).path; | ||
| if (!raw) return; | ||
| const abs = path.isAbsolute(raw) ? raw : path.resolve(ctx.cwd, raw); | ||
| // Only fire when the edited file is inside this project's .pensieve/ | ||
| if (!abs.startsWith(pensieveDir + path.sep) && abs !== pensieveDir) return; | ||
| runSyncGraph(abs, event.toolName, ctx.cwd); |
There was a problem hiding this comment.
The tool_result handler only triggers sync for edit and write, but runSyncGraph() already maps multi_edit to MultiEdit and the header comment mentions MultiEdit. If pi emits a multi_edit tool result, graph sync won’t run. Include multi_edit in the allowed toolName set (or broaden to any file-mutating tools you rely on).
There was a problem hiding this comment.
Acknowledged. These are valid improvements but the current behavior is safe (graceful degradation). Will address in a follow-up.
…name, add mkdir - pi/README.md: remove pensieve-auto-sediment from tree and table, point to pi-sediment - pi/README.md: remove --with-auto-sediment section, update install instructions for settings.json - pi/README.md: update branch name feature/auto-sediment-hook → pi - README.md: update submodule and curl branch refs → pi - pi/install.sh: mkdir -p before writing settings.json
…ment
The 'When to write to Pensieve' section told the LLM to invoke
'/skill:pensieve self-improve' after producing durable insights. In
practice this nudged main-session models to:
1. Race the sediment sidecar (both writers landing the same insight
under different slugs — sediment's dedupe and slug-collision logic
was being bypassed).
2. Frame every task outcome as 'insight-worthy', polluting main-line
reasoning.
3. Manually 'put gbrain' via shell when the gbrain_put tool was
intentionally not registered (defeating the gbrain extension's
2026-05-04 read-only main-session decision).
This change keeps the read guidance intact and makes the write contract
explicit:
- Main session must NOT write/edit under .pensieve/ on its own
- Main session must NOT shell out to 'gbrain put' / 'bun ...gbrain/cli.ts put'
- Sediment is the single writer for both stores
- Exception: user-explicit /skill:pensieve self-improve, /pensieve refine,
'sediment this conclusion' — main session may run the workflow
We considered a tool_call hook with physical block + bypass flag (pi
extension API supports {block:true,reason:...} for tool_call events),
but reviewing the actual incident rate (zero confirmed main-session
writes under the 2026-05-04 gbrain_put removal — only intent that was
caught and corrected mid-conversation) plus the maintenance overhead
of a triggerable bypass for the legitimate self-improve/refine paths
made the prompt-only fix the right tradeoff. If incidents resurface we
can layer the hook on top.
变更
pi/目录:pensieve-context 扩展、pensieve-wand skill、install.shpackage.json(pi package manifest)pi/extensions/pensieve-auto-sediment/(已由独立 pi-sediment 包替代)--no-auto-sediment参数关联