{"id":50938132,"url":"https://github.com/nugget/claude-sessions-to-thane-docroot","last_synced_at":"2026-06-17T11:04:01.769Z","repository":{"id":360996604,"uuid":"1251929563","full_name":"nugget/claude-sessions-to-thane-docroot","owner":"nugget","description":"Export utility to store Claude Code sessions into a thane-ai-agent document root","archived":false,"fork":false,"pushed_at":"2026-05-28T19:00:14.000Z","size":84,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-05-28T20:14:04.066Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/nugget.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-05-28T03:14:35.000Z","updated_at":"2026-05-28T18:17:25.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/nugget/claude-sessions-to-thane-docroot","commit_stats":null,"previous_names":["nugget/claude-sessions-to-thane-docroot"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/nugget/claude-sessions-to-thane-docroot","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nugget%2Fclaude-sessions-to-thane-docroot","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nugget%2Fclaude-sessions-to-thane-docroot/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nugget%2Fclaude-sessions-to-thane-docroot/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nugget%2Fclaude-sessions-to-thane-docroot/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/nugget","download_url":"https://codeload.github.com/nugget/claude-sessions-to-thane-docroot/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nugget%2Fclaude-sessions-to-thane-docroot/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34445186,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-17T02:00:05.408Z","response_time":127,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-06-17T11:03:59.467Z","updated_at":"2026-06-17T11:04:01.761Z","avatar_url":"https://github.com/nugget.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# claude-sessions-to-thane-docroot\n\nExport a Claude Code project's session transcripts into a hierarchy of markdown\ndocuments that form a [`thane-ai-agent`](https://github.com/nugget/thane-ai-agent)\n**document root**.\n\nThe goal is to turn raw session logs into *primary source material* for tracing\nthe ebbs and flows of a project's development — readable by a human and\nindexable by Thane. One markdown document per session, grouped by the git branch\nthe work happened on.\n\n\u003e The binary is `transcript-exporter`. The repo is named for what it does.\n\n## What it reads and writes\n\n- **Reads** the JSONL transcripts Claude Code stores under\n  `~/.claude/projects/\u003cencoded-project-path\u003e/` (and the worktree-sibling\n  directories alongside them).\n- **Writes** a tree of markdown into **one shared Thane document root**, with\n  every project under its own `\u003cagent\u003e/\u003cproject\u003e/` subtree. A single root holds\n  many projects — registering a separate Thane root per project is an\n  operational headache, so don't.\n\n```\n~/Thane/agentic-coding/                        # the document root (one, shared)\n  claude/\n    thane-ai-agent/                            # one project's subtree\n      README.md                                # project overview (document_kind: transcript_root_overview)\n      2026-05/\n        README.md                              # month index, sessions in time order\n        2026-05-26 1431 thane-primary-development.md\n      2026-04/\n        2026-04-29 0915 close-the-verifier-bypass.md\n  codex/\n    some-other-project/                        # a sibling project, exported independently\n```\n\nThe **session** is the unit of continuity, so within a project subtree sessions\nare organized **chronologically** by calendar month (`YYYY-MM/`), one document\nper session named `YYYY-MM-DD HHMM \u003ctitle-slug\u003e.md`. A single session usually\nspans several branches — sometimes interleaved — so branch is *not* the folder:\neach session records every branch it touched in frontmatter and the Overview,\nand marks inline where the working branch switches mid-conversation. Each project\nexport is scoped to its own subtree, so re-running one project never disturbs\nanother.\n\n## Quick start\n\n```sh\n# Build it\njust build            # → ./transcript-exporter\n# …or preview a run straight from source\njust run --project thane-ai-agent --root ~/Thane/agentic-coding --subpath claude/thane-ai-agent --dry-run\n```\n\n```sh\n# Real export (idempotent; safe to re-run)\ntranscript-exporter \\\n  --project thane-ai-agent \\\n  --root    ~/Thane/agentic-coding \\\n  --subpath claude/thane-ai-agent\n```\n\n`--root` is the shared document root; `--subpath` is where this project lands\nwithin it. The root name stamped into every document's `managed_root` derives\nfrom the root's basename (`agentic-coding` here), so it stays identical across\nevery project exported into the same root — nothing to remember, nothing to\ndrift.\n\n`--project` accepts any of: a bare project name (matched against the encoded\nproject dirs), the project's repo path, or the encoded `~/.claude/projects`\ndirectory name.\n\n\u003e For a one-off export, a standalone `--target \u003cdir\u003e` (which is then itself the\n\u003e root) also works. `--root` + `--subpath` is the pattern for the shared root.\n\n### Flags\n\n| Flag | Default | Meaning |\n| --- | --- | --- |\n| `--project` | *(required)* | Which Claude project to export. |\n| `--root` | *(this or `--target`)* | Shared document-root directory; this project lands under `--subpath`. |\n| `--subpath` | `\"\"` | Relative path within `--root` for this project, e.g. `claude/thane-ai-agent`. |\n| `--target` | *(this or `--root`)* | Standalone directory that is itself the document root (alternative to `--root`). |\n| `--root-name` | basename of `--root`/`--target` | Override the Thane root name in frontmatter (`managed_root`) and `\u003croot\u003e:` refs. |\n| `--worktrees` | `true` | Merge worktree-sibling project dirs into the same root. |\n| `--dry-run` | `false` | Report what would change; write nothing. |\n| `--thinking` | `true` | Include assistant thinking blocks in the narrative. |\n| `--max-thinking-chars` | `1600` | Cap per thinking block. |\n| `--max-result-lines` | `30` | Cap tool-result text (lines). |\n| `--max-result-bytes` | `4000` | Cap tool-result text (bytes). |\n| `--timezone` | `Local` | Timezone for document dates: `Local`, `UTC`, or an IANA name. |\n| `--verbose` | `false` | Debug logging. |\n\n## How a session is rendered\n\nEach session document is a *narrative*, not a JSON dump:\n\n- a lead paragraph (the opening request) — **this is what Thane indexes as the\n  document summary**;\n- an **Overview** (time span, every branch touched, working dir, activity counts,\n  tools used, linked PRs, Claude Code version);\n- a **Conversation** section: human prompts (block-quoted), assistant prose,\n  thinking (collapsible), tool calls humanized to one line with their output\n  folded into `\u003cdetails\u003e`, PR links, and `⎇` markers wherever the working branch\n  switches so interleaved cross-branch work stays legible.\n\nChronologically adjacent sessions are linked at the foot of each document\n(← previous / next →), giving the whole corpus a continuous spine across months.\n\n### Forked sessions\n\nForking a session in the Claude UI writes a new transcript that **copies the\nparent's conversation prefix verbatim** — and those copied records keep the\n*parent's* session id; only the divergent tail carries the fork's own id (which\nis the filename). The exporter handles this so forks don't pollute the corpus:\n\n- the canonical session id comes from the **filename**, so each fork gets its\n  own `conversation:` ref (not the parent's);\n- the session is dated from its **divergence point** (first own message), so\n  forks sort to when their work actually happened — not when the parent began;\n- the copied prefix is **not repeated**; the document renders only the fork's\n  own work, led by a fork note that links the parent and a `forked-from:\u003cid\u003e`\n  source-ref (`\u003e 🍴 Forked from [parent] — N earlier messages … not repeated here`).\n\nA non-forked session has no inherited prefix and is unaffected. The detection is\ndata-driven (it only triggers when records actually carry a different id), so it\ndegrades gracefully if Claude Code's fork format ever changes.\n\n## Idempotency / sync model\n\nOutput is a deterministic function of the transcripts, so re-running is a\npush-sync of the project's subtree (each export is confined to its own\n`\u003cagent\u003e/\u003cproject\u003e/` directory):\n\n- a file is written only when its bytes actually change;\n- documents this tool owns (identified by the `generated_by:\n  \"session-transcript-exporter\"` frontmatter marker) that are no longer produced\n  are **deleted**, and emptied directories are pruned;\n- files *without* that marker — your own notes, `.git`, signing material — are\n  never touched.\n\nThe tool performs **no git operations**. If you want Thane's\n`verify_signatures: required` policy, you commit and sign the target directory\nyourself (see below).\n\n\u003e ⚠️ **Privacy.** Transcripts contain everything typed and read during a session\n\u003e — file contents, environment values, occasionally secrets. Review before\n\u003e publishing a transcripts root or pushing it to a remote. Redaction is not yet\n\u003e implemented.\n\n## Wiring the root into Thane\n\nRegister the **one** shared root in Thane's config — not one per project. The\nroot key matches the root name (the `--root` basename you export with):\n\n```yaml\npaths:\n  agentic-coding: ~/Thane/agentic-coding\n\ndoc_roots:\n  agentic-coding:\n    authoring: read_only        # this corpus is generated, not hand-edited\n    git:\n      enabled: true\n      sign_commits: true        # if YOU commit the root with a trusted key\n      verify_signatures: warn   # or: required\n      signing_key: ~/.ssh/id_ed25519\n```\n\nThane walks the whole root, so every project subtree (`claude/\u003cproject\u003e/`,\n`codex/\u003cproject\u003e/`, …) is indexed under the single `agentic-coding` root. Export\neach project with the same `--root`; only `--subpath` changes.\n\nFrontmatter is tailored to Thane's reader on purpose. Its document parser is\n**line-based, not full YAML** — flat keys, scalar / inline-array / block-list\nvalues only, all list values deduped and sorted, keys lowercased. The emitter\nhere only ever produces that subset. The authoritative contract lives in the\n`thane-ai-agent` repo at `docs/understanding/document-roots.md` and\n`internal/state/documents/parser.go`; re-check it before changing the emitted\nfrontmatter.\n\nEmitted frontmatter fields: `title`, `description`, `tags`, `generated_by`,\n`generated_at`, `document_kind`, `refresh_strategy`, `source_refs`\n(`conversation:\u003cid\u003e`, `branch:\u003cb\u003e`, `pr:\u003cn\u003e`, `cwd:\u003cpath\u003e`, `project:\u003cname\u003e`),\n`managed_root`, `created`, `updated`.\n\n## How it works (architecture)\n\nThe pipeline is five small packages, each a stage:\n\n```\nclaudeproj  →  transcript  →  export (place)  →  render  →  docroot (sync)\n locate         parse           group/order      markdown    reconcile target\n```\n\n| Package | Responsibility |\n| --- | --- |\n| `internal/claudeproj` | Resolve a `--project` to its `~/.claude/projects` dir(s); enumerate session `.jsonl` files (worktree siblings merged, subagent sidecars skipped). |\n| `internal/transcript` | Tolerant JSONL → `Session` model: prompts, assistant text/thinking, tool calls paired with results, branches, PRs, titles. |\n| `internal/render` | `Session` → markdown. Frontmatter emitter (Thane's subset), per-session narrative, branch index, root overview. Slugging, branch→dir mapping, filenames. |\n| `internal/export` | Orchestration: parse all sessions, group by home branch, order chronologically, assign collision-free filenames, render, hand the file set to the sync layer. |\n| `internal/docroot` | Idempotent reconcile of the desired file set against the target directory (write-on-change, marker-scoped delete, prune). |\n| `cmd/transcript-exporter` | Flag parsing and wiring. |\n\n### Transcript-format quirks worth knowing\n\nThese are the non-obvious things the parser handles — useful when something\nlooks wrong:\n\n- **Out-of-order tool I/O.** When tools run in parallel, Claude Code can log a\n  `tool_result` *before* its `tool_use`. Results that arrive early are buffered\n  (`pending` map in `parse.go`) and attached when the call appears.\n- **PR spam.** `pr-link` entries repeat on every CI poll; PRs are deduped per\n  session.\n- **Titles.** Come from `custom-title` / `ai-title` entries (not a `summary`\n  entry). Resolution order: custom → AI → first prompt → session id.\n- **Subagents.** Live in a `\u003csessionId\u003e/subagents/` sidecar. Their internal\n  steps are *not* rendered today; the `Task` tool's result already carries the\n  subagent's returned report, which is what the narrative shows.\n- **Injected envelopes.** `\u003csystem-reminder\u003e`, `\u003clocal-command-stdout\u003e`, etc.\n  are stripped from user text; slash-command invocations are surfaced as\n  `Ran command /foo`.\n\n## Extending it\n\nCommon changes and where they live:\n\n- **Make a tool render better** (e.g. a new MCP tool, or richer Bash output):\n  `humanizeTool` in `internal/transcript/tools.go`. Return a one-line summary,\n  optional detail, and (for dispatchers) a `SubagentRef`.\n- **Change the directory layout or filenames:** `placeSessions` /\n  `monthDir` / `sessionFileName` in `internal/export/export.go`, plus `Slug` in\n  `internal/render/slug.go`.\n- **Change how branch switches appear:** `writeBranchMarker` /\n  `writeConversation` in `internal/render/session.go`.\n- **Add or change a frontmatter field:** `Frontmatter` + `Render` in\n  `internal/render/frontmatter.go`, and the builders in `render/session.go` /\n  `render/index.go`. Keep to Thane's parseable subset (flat keys; scalars or\n  block lists).\n- **Render subagent internals:** the sidecars are already enumerated by\n  `claudeproj`; parse `\u003csessionId\u003e/subagents/agent-*.jsonl` and splice into the\n  `Task` tool-call event.\n\n**Invariant to preserve: determinism.** Output must be a pure function of the\ntranscripts so the sync stays a clean no-op. Never put wall-clock time in a\ndocument (`generated_at` is the session's *end* time, not \"now\"). If you add a\nfield, derive it from the data.\n\n## Development\n\n```sh\njust            # list recipes\njust test       # go test -race ./...\njust lint       # golangci-lint (v2 config in .golangci.yml)\njust ci         # full gate: fmt-check, vet, mod-tidy-check, lint, test\n```\n\nStandard library only — no third-party dependencies. CI runs `just ci` on push\nand PR (`.github/workflows/ci.yml`).\n\n## License\n\nApache 2.0. See [LICENSE](LICENSE).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnugget%2Fclaude-sessions-to-thane-docroot","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fnugget%2Fclaude-sessions-to-thane-docroot","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnugget%2Fclaude-sessions-to-thane-docroot/lists"}