Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 19 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -102,14 +102,30 @@ ccx treats all agent data as **read-only**. Writes only to its own directories:
- `$XDG_CONFIG_HOME/ccx/` — config
- `$XDG_DATA_HOME/ccx/` — stars database

## Claude Code skill
## Claude Code skills

ccx ships as a Claude Code skill. Install alongside the binary:
ccx ships with two Claude Code skills:

- **ccx** — Session viewer. Browse, search, export sessions from inside Claude Code.
- **ccx-fold** — Session decision extraction. Fold a session into auditable decisions (HTML for humans) and durable knowledge-base entries (markdown for agents). Tracks decision provenance: who decided, what, why, what was rejected.

Install alongside the binary:
```bash
curl -fsSL https://raw.githubusercontent.com/thevibeworks/ccx/main/install.sh | bash
```

Or manually copy `skills/ccx/` to `~/.claude/skills/ccx/`.
Or manually copy skills to `~/.claude/skills/`:
```bash
cp -r skills/ccx/ ~/.claude/skills/ccx/
cp -r skills/ccx-fold/ ~/.claude/skills/ccx-fold/
```

Usage:
```bash
/ccx-fold # Fold most recent session
/ccx-fold <session-id> # Fold specific session
/ccx-fold --dry-run # Preview without writing
```

## Credits

Expand Down
134 changes: 134 additions & 0 deletions docs/design/0001-ccx-fold.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
# Design: ccx-fold — Session Decision Extraction

**Created**: 2026-05-20
**Status**: Accepted
**Skill**: `skills/ccx-fold/`

## Problem

Three systems capture different slices of "what happened and why" in
human-agent collaboration, and none capture the whole picture:

| System | Input | Captures | Misses |
|---|---|---|---|
| ccx | Session JSONL | Who said what, when | What actually shipped |
| dev-vibe-fold | Git log | What shipped | Why it was done that way |
| phase-act (PDCA) | process.jsonl | Structured decisions | Requires SCRUM lane discipline |

The raw session transcript — where thinking and deciding actually happen —
is treated as an audit trail nobody reads. Knowledge evaporates when the
context window compacts or the session ends.

## Core insight

A coding session is a decision stream buried in 1:20 signal-to-noise.
"Folding" collapses this into two artifacts:

1. **For humans**: a reviewable decision trail (fold.html)
2. **For agents**: a compounding knowledge base (.ccx/knowledge/)

The fold is the closing move of a session — raw transcript in,
structured knowledge out.

## Key design decisions

### Session-first, not git-first

Unlike dev-vibe-fold (starts from `git log`), ccx-fold starts from the
session transcript. This captures WHY. Git changes are correlated as
evidence, not as the primary source.

### Decision provenance is the product

The central question is not "what changed" but "who decided what and why."
Every decision is classified: human / agent / joint / correction.

### Three-gate quality bar (from Matt Pocock's ADR pattern)

Before any KB entry: (1) hard to reverse, (2) surprising without context,
(3) real tradeoff. All three required. Prevents the KB from becoming a
session diary.

### Deletion test

After drafting a KB entry: "would the next agent make a worse decision
without this?" If derivable from code or conventions, delete the draft.

### Scene reconstruction (from /reflect)

High-attention decisions use the reflect model: tension, observation,
decision, tradeoff, next. Don't log facts — reconstruct the moment.

### Dual-format output (from html-effectiveness research)

Markdown dies at ~100 lines for human review. HTML survives via spatial
hierarchy, collapsible sections, and visual badges. Agents need structured
markdown with frontmatter. Different audiences, different formats.

### Compounding KB (from Karpathy LLM Wiki)

Each fold adds to a persistent knowledge base. Index, log, cross-references,
superseding, and lint keep it healthy. The fold is an ingest operation, not
a disposable report.

## Alternatives considered

### Extend dev-vibe-fold to read sessions

Rejected: dev-vibe-fold is git-centric by design. Bolting session parsing
onto it would violate its architectural assumption (commits are the source
of truth). Better to keep them complementary — different primary sources,
compatible knowledge formats.

### Build into ccx Go binary

Partially accepted: the Go binary can do deterministic work (parsing,
correlation, HTML scaffolding). But decision classification requires LLM
reasoning. The skill calls the Go binary for parsing and adds its own
classification layer. Future: `ccx fold` CLI command for the deterministic
parts.

### Single output format (markdown only)

Rejected: html-effectiveness research shows structured HTML materially
improves human engagement at document lengths >100 lines. A fold with
9 decisions, excerpts, and diffs easily exceeds that. Agents need
structured markdown. Two audiences, two formats.

### Knowledge entries in CLAUDE.md memory

Rejected: CLAUDE.md memory is flat (no index, no lint, no superseding).
The knowledge base needs structure to compound. Entries could optionally
be copied to memory, but the KB is the source of truth.

## Influences

| Source | What we took |
|---|---|
| dev-vibe-fold | Fold as closing move; pattern harvest; retro note format |
| phase-act.md | 4-tier attention model (high/mid/low + metadata) |
| /reflect skill | Scene reconstruction: tension/observation/decision/tradeoff/next |
| dev-log-writer | Decision table format; tag system; "name for knowledge, not method" |
| Karpathy LLM Wiki | Compounding KB; index.md + log.md; ingest/query/lint operations |
| html-effectiveness | Self-contained HTML; spatial > sequential; ~100 line readability cliff |
| Matt Pocock skills | Three-gate ADR bar; CONTEXT.md shared language; write-a-skill structure |
| ccx | Session tree model; read-only philosophy; single-binary; CSS palette |

## File structure

```
skills/ccx-fold/
SKILL.md 129 lines Process, modes, error handling
EVIDENCE.md 131 lines Inputs, evidence graph, citations
DECISIONS.md 137 lines Detection, provenance, three-gate bar
HTML-REPORT.md 145 lines Page structure, card templates
ARCHIVE.md 242 lines KB format, naming, promotion, lint
```

## Future work

- `ccx fold` Go CLI command for deterministic parsing + HTML generation
- CONTEXT.md vocabulary drift detection during fold
- Cross-project knowledge linking
- Team-level KB aggregation
- Automated fold trigger via Claude Code hooks
150 changes: 150 additions & 0 deletions internal/cmd/fold.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
package cmd

import (
"encoding/json"
"fmt"
"os"
"os/exec"
"path/filepath"
"time"

"github.com/spf13/cobra"

"github.com/thevibeworks/ccx/internal/fold"
"github.com/thevibeworks/ccx/internal/parser"
"github.com/thevibeworks/ccx/internal/provider"
)

var foldCmd = &cobra.Command{
Use: "fold [session]",
Short: "Fold a session into structured decisions",
Long: `Analyze a session and produce structured decision data.

Parses the session into turns, detects corrections, tracks file mutations,
and correlates with git commits in the session time window. Outputs JSON
for consumption by the ccx-fold Claude Code skill or other tools.

Examples:
ccx fold # Latest session, JSON to stdout
ccx fold e38536 # Specific session
ccx fold -o fold.json # Write to file
ccx fold --html # Also generate HTML review to temp dir`,
Args: cobra.MaximumNArgs(1),
RunE: runFold,
}

var (
foldOutput string
foldProject string
foldHTML bool
foldAll bool
)

func init() {
foldCmd.Flags().StringVarP(&foldOutput, "output", "o", "", "output file (default: stdout)")
foldCmd.Flags().StringVarP(&foldProject, "project", "p", "", "project name")
foldCmd.Flags().BoolVar(&foldAll, "all", false, "search across all projects")
foldCmd.Flags().BoolVar(&foldHTML, "html", false, "also generate HTML review in temp directory")
}

func runFold(cmd *cobra.Command, args []string) error {
backend := provider.Default()

var session *parser.Session
var err error

if len(args) == 0 {
session, err = selectSession(backend, foldAll)
} else {
projectName, sessionID := parseSessionArg(args[0])
if foldProject != "" {
projectName = foldProject
}
query, qErr := sessionLookupQuery(projectName, foldAll)
if qErr != nil {
return qErr
}
session, err = resolveSessionInQuery(backend, query, sessionID)
}
if err != nil {
return fmt.Errorf("session: %w", err)
}
if session == nil {
return fmt.Errorf("no session found")
}

fullSession, err := backend.ParseSession(session.FilePath)
if err != nil {
return fmt.Errorf("parse: %w", err)
}

result := fold.Analyze(fullSession)

repoDir := findGitRoot()
if repoDir != "" {
if err := fold.CorrelateGit(result, repoDir); err != nil {
fmt.Fprintf(os.Stderr, "warning: git correlation failed: %v\n", err)
}
}

jsonBytes, err := json.MarshalIndent(result, "", " ")
if err != nil {
return fmt.Errorf("marshal: %w", err)
}

if foldOutput != "" {
dir := filepath.Dir(foldOutput)
if dir != "." && dir != "" {
if err := os.MkdirAll(dir, 0755); err != nil {
return fmt.Errorf("mkdir: %w", err)
}
}
if err := os.WriteFile(foldOutput, jsonBytes, 0644); err != nil {
return fmt.Errorf("write: %w", err)
}
fmt.Fprintf(os.Stderr, "Fold written to: %s\n", foldOutput)
} else {
fmt.Println(string(jsonBytes))
}

if foldHTML {
if err := generateFoldHTML(result, fullSession); err != nil {
fmt.Fprintf(os.Stderr, "warning: HTML generation failed: %v\n", err)
}
}

return nil
}

func generateFoldHTML(result *fold.FoldResult, session *parser.Session) error {
tmpDir := os.TempDir()

slug := session.Slug
if slug == "" && len(session.ID) > 8 {
slug = session.ID[:8]
}
filename := fmt.Sprintf("ccx-fold-%s-%s.html",
time.Now().Format("20060102-150405"), slug)
htmlPath := filepath.Join(tmpDir, filename)

html := fold.RenderHTML(result)

if err := os.WriteFile(htmlPath, []byte(html), 0644); err != nil {
return err
}

fmt.Fprintf(os.Stderr, "HTML review: %s\n", htmlPath)
openBrowser(htmlPath)
return nil
}

func findGitRoot() string {
cmd := exec.Command("git", "rev-parse", "--show-toplevel")
out, err := cmd.Output()
if err != nil {
return ""
}
return filepath.Clean(string(out[:len(out)-1]))
}

// openBrowser is defined in web.go
1 change: 1 addition & 0 deletions internal/cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ func init() {
rootCmd.AddCommand(exportCmd)
rootCmd.AddCommand(configCmd)
rootCmd.AddCommand(doctorCmd)
rootCmd.AddCommand(foldCmd)
}

func initConfig() {
Expand Down
Loading
Loading