SLOPSHOPPER

self-compact

No description.

newguardtool
A shopper browsing a rack in a slop shop
README

dotfiles

chezmoi と home-manager (Nix) ベースで構築された個人用dotfiles.

  • chezmoi:
  • 設定ファイル(~/.zshrc, ...etc)をリポジトリ管理
  • home-manager:
  • Nixでグローバルに追加するツールを管理

ディレクトリ構造

.chezmoiroot           # chezmoi/ をソースディレクトリとして指定
chezmoi/               # chezmoi 以下が同期される設定ファイル群
  dot_agents/          # → ~/.agents
  dot_claude/          # → ~/.claude
  dot_zshrc.tmpl       # → ~/.zshrc
  private_dot_config/  # → ~/.config/
    home-manager/      # home-manager 設定
shell/                 # 共有シェルスクリプト (.zshrc から読み込み)
scripts/               # セットアップ・メンテナンス用スクリプト
config/                # $HOME 以下に同期はしない設定ファイル (starship, MCP)

セットアップ

bash -c "$(curl -fsLS https://raw.githubusercontent.com/d-kimuson/dotfiles/refs/heads/main/scripts/setup.sh)"

上記を実行することで

  1. chezmoi がインストールされ、設定が配布される
  2. Nix & home-manager がインストールされる
  3. home-manager によって各種ツールがインストールされる

の流れでセットアップが完了。

exec $SHELL -l

でシェル設定も読み込んでおく。

メンテナンス

設定を反映したい

dotfiles-apply

chezmoi apply + home-manager switch + シェル再読み込みが実行される。

nix 管理のツールをアップデートしたい

./scripts/update.sh

mise, direnv, zed-editor は stable pin、それ以外は主に unstable を利用している。

環境固有の設定を追加したい

shell/localrc.sh を追加し、環境変数等追加したい設定を記載

export TERM=xterm-256color

同期で管理できない設定ファイルの配布

internal-cli mcp deliver
internal-cli pi-agent deliver
internal-cli merge-config
  • internal-cli mcp deliver: config/mcp.template.json を各種 MCP 設定へ配布
  • ~/.pi/agent/ は必要に応じて自動作成
  • ~/.claude.json
  • ~/Library/Application Support/Claude/claude_desktop_config.json
  • ~/.codex/config.toml
  • ~/.pi/agent/mcp.json

pi-agent のマシン別設定

config/pi-agent/ の共有設定とモデルプロファイルをベースに、git 管理しない local 設定を重ねて ~/.pi/agent/ へ配布する。

  • config/pi-agent/providers.local.json: このマシンで利用可能な provider を availableProviders で指定
  • config/pi-agent/settings.json + model profile + config/pi-agent/settings.local.json → ~/.pi/agent/settings.json
  • config/pi-agent/models.json + existing target + config/pi-agent/models.local.json → ~/.pi/agent/models.json
  • config/pi-agent/agents/frontend_worker.md + design profile + config/pi-agent/agents/frontend_worker.local.json → ~/.pi/agent/agents/frontend_worker.md
{
  "availableProviders": ["opencode-go", "openai-codex"]
}

local 設定は top-level key 単位でマージされ、model profile から enabledModels / defaultProvider / defaultModel / agent ごとの model / fallbackModels / thinking が展開される。

models.json は既存の ~/.pi/agent/models.json を保持しつつ、provider ごとの models を id で upsert する。dry-run では secret を含む可能性がある target 内容は表示しない。

Source 1 files
hooks/register.ts 94 lines
1// Gives Claude a `compact` tool to compact its own context and keep working.
2// $.session.compact() rejects while a turn runs, so the tool only records the
3// request. The compaction runs once the turn completes, and a prompt carrying
4// `resume` starts the next turn.
5import type { On, SessionCompactArgs, SessionCompactResult } from "claude-code"
6
7const TOOL = "mcp__self-compact__compact"
8
9const DESCRIPTION = [
10  "Compact your own conversation context to free space and keep working.",
11  "The compaction runs right after this turn ends, so end your turn right after calling this tool.",
12  "`resume` is then sent to you as the next prompt: put in it what you need to continue the task.",
13].join(" ")
14
15const INPUT_SCHEMA = {
16  type: "object",
17  properties: {
18    instructions: { type: "string", description: "What the summary must keep or drop" },
19    resume: { type: "string", description: "The prompt to continue with after the compaction" },
20  },
21  required: ["resume"],
22}
23
24type Request = { instructions: string | undefined; resume: string }
25
26type Outcome =
27  | { kind: "compacted" }
28  | { kind: "skipped"; reason: string }
29  | { kind: "failed"; reason: string }
30
31type Compact = (args: Pick<SessionCompactArgs, "instructions">) => Promise<SessionCompactResult>
32
33const isNonEmptyString = (value: unknown): value is string => typeof value === "string" && value.trim() !== ""
34
35// Decides the prompt that starts the next turn from how the compaction ended
36const followUp = (outcome: Outcome, resume: string): string => {
37  switch (outcome.kind) {
38    case "compacted":
39      return resume
40    case "skipped":
41      return `The context was not compacted: ${outcome.reason}\n\n${resume}`
42    case "failed":
43      return `The context compaction failed: ${outcome.reason}\n\n${resume}`
44  }
45}
46
47const compact = async (run: Compact, instructions: string | undefined): Promise<Outcome> => {
48  try {
49    const result = await run(instructions === undefined ? {} : { instructions })
50    return result.skip === undefined ? { kind: "compacted" } : { kind: "skipped", reason: result.skip }
51  } catch (error) {
52    return { kind: "failed", reason: error instanceof Error ? error.message : String(error) }
53  }
54}
55
56// The request recorded by the tool call, consumed when the main loop's turn completes
57let pending: Request | null = null
58
59export function register(on: On) {
60  on("session.start", async ($, e, next) => {
61    await $.tool.register({ name: "compact", description: DESCRIPTION, inputSchema: INPUT_SCHEMA })
62    return next(e)
63  })
64
65  // An MCP-style tool waits behind ToolSearch by default; keep it in the prompt's list
66  on("tool.describe", { tool: TOOL }, async ($, e, next) => ({ ...(await next(e)), isDeferred: false }))
67
68  on("tool.call", { tool: TOOL }, async ($, e) => {
69    // A subagent's context is not the session's, so only the main loop may compact
70    if (e.agentId !== undefined) {
71      return { result: "Not scheduled: only the main conversation can compact the session context." }
72    }
73    if (!isNonEmptyString(e.resume)) {
74      return { result: "Not scheduled: pass `resume`, the prompt to continue with after the compaction." }
75    }
76    pending = { instructions: isNonEmptyString(e.instructions) ? e.instructions : undefined, resume: e.resume }
77    return { result: "Compaction scheduled. End your turn now; `resume` starts the next turn after the compaction." }
78  })
79
80  on("turn.complete", async ($, e, next) => {
81    const result = await next(e)
82    // A subagent's turn also completes here; only the main loop's turn ends the request's turn
83    if (e.agentId === undefined && pending !== null) {
84      const request = pending
85      pending = null
86      // Detached: the compaction cannot start until this turn has finished
87      void compact((args) => $.session.compact(args), request.instructions).then((outcome) =>
88        $.prompt.submit({ text: followUp(outcome, request.resume) }),
89      )
90    }
91    return result
92  })
93}
94