skills/docs/approach/2026-01-24-session-hygiene.md

5.3 KiB

Approach: Session Hygiene Extension

Strategy

Core philosophy: Ambient awareness, not active management.

The extension provides a persistent footer widget showing git state. The user glances at it when they want to. A /commit command offers a guided flow with auto-drafted messages when they're ready to commit. No nudges, no prompts, no interruptions.

Key Decisions:

  1. Widget vs Status: Widget (multi-character, always visible) vs setStatus (footer slot, subtle) → Widget — needs to be glanceable without hunting for it

  2. Polling vs Events: Poll git status periodically vs hook into tool_result events → Hook tool_result — only re-check after bash/write/edit tools that might change files. Avoids polling overhead.

  3. Grouping strategy: No grouping vs LLM-driven grouping → LLM-driven grouping — LLM sees changed files + session context, proposes logical groups with conventional commit messages. Always runs, even for 1-3 files.

  4. Confirmation flow: Always confirm vs LLM discretion → LLM discretion — LLM decides when to ask questions (ambiguous grouping, orphan files) vs proceed. User already invoked /commit, so trust the intent.

  5. Orphan files: Auto-bucket into "misc" vs ask → Ask — if a file doesn't fit any logical group, LLM should ask user where it belongs.

  6. Staging: Auto-stage all vs let user stage manually → Auto-stage all (git add -A) — matches "just commit everything" simplicity. User can unstage manually before /commit if needed.

Architecture

New Components

~/.pi/agent/extensions/session-hygiene/
├── index.ts          # Extension entry point
└── git.ts            # Git helpers (status, commit, etc.)

Extension Structure

// index.ts
export default function(pi: ExtensionAPI) {
  // State
  let dirtyCount = 0;
  
  // 1. Widget: show dirty count in footer
  pi.on("session_start", updateWidget);
  pi.on("tool_result", maybeUpdateWidget);  // Only after bash/write/edit
  
  // 2. Command: /commit
  pi.registerCommand("commit", { handler: commitFlow });
}

Data Flow

[tool_result event]
       │
       ▼
  is bash/write/edit?
       │ yes
       ▼
  git status --porcelain
       │
       ▼
  count changed files
       │
       ▼
  ctx.ui.setWidget("hygiene", ["● 14 files"])
[/commit command]
       │
       ▼
  git status --porcelain → list of changed files
       │
       ▼
  extract session context:
    - recent messages (user prompts, assistant responses)
    - file touchpoints (which files were read/written/edited when)
       │
       ▼
  LLM prompt:
    "Here are the changed files and session context.
     Group into logical commits. For each group:
     - list files
     - conventional commit message
     If a file doesn't fit, ask the user.
     If grouping is ambiguous, ask.
     Otherwise, proceed and execute commits."
       │
       ▼
  LLM executes commits via tool calls (git add <files>, git commit -m "...")
       │
       ▼
  update widget (now shows 0 or remaining)

Commit Tool

The /commit command injects context and lets the LLM drive. It needs a git_commit tool:

pi.registerTool({
  name: "git_commit",
  description: "Stage specific files and commit with a message",
  parameters: Type.Object({
    files: Type.Array(Type.String(), { description: "Files to stage (relative paths)" }),
    message: Type.String({ description: "Commit message (conventional format)" }),
  }),
  async execute(toolCallId, params, onUpdate, ctx, signal) {
    // git add <files...>
    // git commit -m <message>
    // return success/failure
  },
});

This lets the LLM make multiple commits in sequence, asking questions in between if needed.

Risks

Known Unknowns

  • Widget placement: setWidget defaults to above editor. Need to verify belowEditor placement looks right for a small status indicator.
  • LLM latency: Drafting commit message adds a few seconds. Acceptable? Could show "Drafting..." in UI.
  • Model availability: Need a model for commit message drafting. What if user doesn't have API key for it?

Failure Modes

  • Not a git repo: git status returns non-zero. Extension silently does nothing (no widget, /commit shows error).
  • Detached HEAD / merge conflict: Unusual git states. /commit should detect and warn rather than corrupt state.
  • Empty commit: All changes already staged and committed. /commit should detect "nothing to commit" and notify.

Blast Radius

  • Minimal: Extension only reads git state and runs git add -A + git commit. No force pushes, no rebase, no destructive operations.
  • Worst case: User commits something they didn't want to. Recoverable via git reset HEAD~1.

Phases

Phase 1: Widget + git_commit Tool

  • Footer widget showing dirty file count
  • Updates after file-mutating tools
  • git_commit tool registered (LLM can use it anytime)

Phase 2: /commit Command

  • /commit command injects context and triggers LLM-driven grouping
  • LLM proposes groups, asks questions if uncertain, executes commits
  • Widget updates as commits land

Phase 3: Polish (Future)

  • Stash support (/stash)
  • Undo last commit (/uncommit)
  • Integration with worklog skill (prompt to commit after worklog)