CtrlK
BlogDocsLog inGet started
Tessl Logo

process

Process work sessions into the claude-code-plugins knowledge graph — preserves the session transcript, writes or links its chronicle entry as the session's synthesis, turns the transcript into observations, updates the patterns and decisions they support, folds in named plugins and skills, and proposes promotions to the personal commons. Also resolves-and-preserves an on-demand Claude Code docs URL when one comes up. Use when the user says "process this session", "run /process", names a session transcript or UUID, names a chronicle entry whose session should be processed, asks what's in the processing queue, or points at a Claude Code release-notes / docs page to capture.

Invalid
This skill can't be scored yet
Validation errors are blocking scoring. Review and fix them to unlock Quality, Impact and Security scores. See what needs fixing →
SKILL.md
Quality
Evals
Security

process

This is the orchestrator for the claude-code-plugins knowledge graph. It turns raw material — session transcripts, and the occasional Claude Code docs page — into graph notes, via one human-approved plan per run. It does not enforce anything beyond that approval: the graph's health rests on the conventions in the sibling knowledge-graph skill and on the plan you read before it runs.

Four things happen to a session, and the shape matters:

  1. Take it in (step 2) — resolve the input to a canonical session:{uuid}.
  2. Preserve it (step 2) — write the source note and commit the rendered transcript.
  3. Synthesize it (steps 5 and 6) — the chronicle entry, written for a human reader.
  4. Extract from it (step 4) — observations, from the transcript.

Stages 3 and 4 are siblings, both made from the transcript; neither is upstream of the other. The chronicle is not a filter the observations pass through, and it is not an input tier. It was one, and everything that arrangement required — an ordering between two tiers, a guard against double-counting a session through both — existed only to contain the inversion.

Values written in like this were filled from .commons.yml when this skill was generated by graph-init. They are concrete names now, not placeholders — read them as this graph's actual type names, directories, and sinks.

1. Entry

/process <input> takes a session (a transcript path, a UUID, or "this session"), a URL (a Claude Code docs page), or pasted text naming one piece of raw material. A chronicle entry is not an input: it is this pipeline's own output, and naming one means processing the session it describes.

/process with no argument enumerates the queue instead of processing anything. One tier has a queue:

  • Session — every *.jsonl directly inside a directory matching ~/.claude/projects/*claude-code-plugins*/ with no source note carrying its session:{uuid}. Preview each by date and size; a bare UUID tells the reader nothing.

The ledger test is the recorded source: value, not a filename mention. An identifier can appear in the prose of some other source note without having been processed; grepping for it rather than for source: <value> reports it as done when it isn't.

List entries human-readably, never as bare identifiers. Show the queue and stop — running it is a separate, explicit invocation per item (or "process all of these"), not implied by listing. The claude-code-docs tier arrives by hand (a pasted URL — nothing to glob), so it has no queue: ask the user to paste or name the docs page instead.

2. Resolve

Match the input to one of this graph's source tiers and normalize it to a canonical source: identity — the value the ledger will key on.

Session. Input is a transcript path, a session UUID, or a request to process a session by date — including "this session". Transcripts live directly inside ~/.claude/projects/-Users-derek-personal-Developer-claude-code-plugins/ as <uuid>.jsonl, with worktree sessions in sibling directories whose slug carries the worktree suffix. Check all of them. Subagent transcripts nest one level down, in a directory named for the parent session's UUID — they are not sessions and are never queued.

Canonical source: is session:{uuid}, not the file path: a transcript moves between those directories when a worktree is created or removed, and a path key would resolve to a second, duplicate source note for the same session.

Resolve by invoking meta-claude:session-export to render the JSONL into readable text — raw JSONL is unreadable at this size and burns context for nothing. Name the rendered file YYYY-MM-DDThhmm-<short-description>.txt and send it to a scratch directory outside the repo, not to knowledge/sources/raw/ — the gate below runs on it there, and only a passing export is moved into the archive. State that destination when invoking the skill so it doesn't have to ask. Transcripts are always large, so this tier always takes the subagent-fanout path in step 4.

The chronicle is not a source tier. A chronicle entry is the session-level synthesis — this pipeline's own output, written for a human reader — so it lives in types.synthesis, not in sources:. It used to be registered as a second, lower-ranked source tier, with an ordering between the two and a guard against double-counting the same session through both. All of that existed to contain one inversion: a distillation sitting on the input side. There is one raw source for a session, and it is the transcript.

A session is a conversation, not a project's property, and the glob above assumes otherwise. A single session can span several repositories — the founding one ran 590 turns in commons and 190 in claude-code-plugins, across four working directories and three branches. Its transcript gets exactly one home regardless, and which one is close to arbitrary. That one landed under a commons worktree slug, where the *claude-code-plugins* pattern could never see it, and the graph concluded from the empty result that the transcript had been pruned — a claim that reached this skill, a decision note, and a spec before anyone checked. It was intact the whole time; it has since been moved here and archived at ~/Developer/session-archive/.

So: before concluding any transcript is gone, search ~/.claude/projects/*/ by UUID, not by project glob. An empty result from the narrow pattern is evidence about the pattern. When a cross-project session turns out to live elsewhere, moving it under the slug of the project that was its primary subject makes it reachable — but that is a repair, not a rule, and the next such session will land wherever it lands.

When the transcript is genuinely gone. Transcripts do get pruned, and this is the real case the fallback tier was invented for. Where a session's transcript no longer exists but its chronicle entry does, the run continues from the chronicle: extraction reads it instead of the transcript, and the source note records raw: unavailable with the reason and the date the absence was observed — so the graph shows a degraded note rather than leaving a reader to wonder later why it is thin. This is a permanent path, and it is the only route by which a pre-existing chronicle entry whose session is gone can be processed at all.

It fires only when the transcript is verifiably absent, and "verifiably" carries the whole weight — a UUID search across ~/.claude/projects/*/, per the paragraph above, not a project-scoped glob that came back empty. That distinction is not hypothetical: this graph marked its founding source note raw: unavailable on the strength of a scoped miss, and the transcript was 2.8 MB and intact. Never fire this path because a transcript is merely large, or because the chronicle is an easier read. Extracting from the chronicle while the transcript is alive is exactly the failure step 4 exists to prevent, and this path must not become the loophole for it. Where neither the transcript nor a chronicle entry exists, there is nothing to process: say so and stop.

Claude Code docs (on-demand). Input is a URL to a Claude Code release-notes or feature-docs page, handed in when a session references one. Canonical source: is the URL itself, normalized (strip tracking params, resolve redirects). Fetch it with WebFetch — fetched material can vanish, which is why the preserve step below keeps what mattered rather than a link to it. No queue for this tier; it is only ever processed when explicitly handed in.

Preserve — write the source note, keep the raw material

Before anything is inspected, write the source note and put the raw material somewhere it will still be there in a year. This pipeline used to have no such step — step 3 searched for a source note and step 9 stamped one, and nothing created one. For the docs tier the gap is invisible, because a fetched page is small enough to inline and an implementer fills in the obvious. For a session it is not.

Sessions archive; docs pages inline.

  • Session. A multi-megabyte transcript cannot live inside a note. The rendered export ends up in knowledge/sources/raw/, committed with the repo — by way of a scratch path and the gate below, never written there directly — and the source note carries identity, date, a one-line description, the processed: stamp, and an archive: pointer to that file.
  • Claude Code docs. Small and self-contained: what mattered from the fetched page goes verbatim into the body of the source note under sources/, with no separate archive.

The archive is committed, and that is the whole fix. Its predecessor was .claude/session-exports/, gitignored as a derived artifact — and a directory outside version control is where a source note's pointer comes to dangle. This graph's founding transcript, session:284b79f5-c34f-4ad3-b97d-9c78cdc9c46f, spent months in no repository, backed up by nothing, its originating worktree deleted and its slug outside the resolver's glob: intact, but defended by nothing and reachable only by someone searching outside the pattern. An archive under knowledge/ is versioned and backed up wherever this repo is, which is the difference between material that survived and material that is kept.

This stage writes before the step-5 plan is approved, deliberately. Every other write in the run waits for that gate. Preservation does not, because it is the one stage whose omission destroys something: a run abandoned at plan review should still have kept the transcript. It writes the source note and the archived file — never observations, never attractors, never the stamp. The processed: stamp goes on at step 9, so an abandoned run leaves an unstamped source note, which is the recoverable state.

It writes; it does not stage or commit. The archived file lands in the working tree and this repo's normal flow commits it, so the raw material rides in the same commit as the notes the run produced.

Preserving twice must not overwrite. A re-run resolves to the same session:{uuid} and arrives back here. If a source note already carries this source:, leave it alone but for anything genuinely missing (an archive: pointer it lacks); never replace its body, and never touch its processed: history. If the archived file already exists, leave it. Step 3 reads that history and decides what this run has left to do.

The gate before anything is archived

This repository is public. That is the assumption this gate was written under, stated here rather than left to be inferred — a repo that is private today can be opened later and no run would notice. Two checks run over the rendered export, and both must pass before the file is written into knowledge/sources/raw/.

Export to a scratch path first, never straight into knowledge/sources/raw/. Tell meta-claude:session-export to write outside the repo — a temp directory — and scan it there. A transcript exported directly into the archive is already in the committed tree before the gate has run, and "withhold" then means deleting a file rather than never writing one. It moves into knowledge/sources/raw/ only once both checks pass.

  1. The floor. Run plugins/knowledge-commons/scripts/scan-secrets.sh over the rendered export. (The repo-relative path is deliberate: this project is the plugin's source, so the working copy is always the current scan, where a path into the installed plugin cache would name whatever version happened to be installed.) It is a fixed scan for credential shapes — private-key blocks, known token prefixes, credential assignments carrying a real value, connection strings with a password, Authorization: headers with a bearer value. It exits non-zero on a hit and non-zero on its own failure, and here those mean the same thing: stop. No archive file, no archive: pointer, nothing written. A scan that could not run is not a scan that passed.
  2. The read-through. Hand the rendered export to a subagent to read for what patterns cannot catch: third-party and client names, personal details about anyone who did not consent to appearing in a public repo, unreleased plans, anything whose sensitivity is semantic rather than shaped. Patterns miss meaning; an agent read is judgment that varies run to run and cannot be proven to work. Neither half is sufficient alone, which is why there are two.

Resolve each hit on its own, with its surrounding context shown, as redact (replace the value with a marker and archive the rest), withhold (this transcript is not archived at all), or accept (a false positive). Record every redaction and every withholding on the source note, so a degraded archive says so in the graph instead of looking complete. Blanket-withholding throws away a whole session over one false positive; blanket-accepting is not a gate.

If the hits cannot be put to a human — a non-interactive invocation, an unattended run — withhold. Do not archive on the assumption that someone will review it later. This repo is public and the failure is irreversible: git history, GitHub's caches, and forks all outlive a later deletion.

3. Find the ledger

Search the graph for the source note carrying this source:. Source notes are the only note type that carries a source: field, so a targeted grep across sources/ is enough — do not build an index for this.

  • Found: enter augment mode. Read its processed: stamp and the notes it already produced (the stamp's ran: list names them). Work out what's new by reading the current content against what's already captured — never by hashing or diffing the raw file. Tell the user plainly what already exists rather than re-proposing it.
  • Not found: this is a first pass. Proceed to inspection.
  • Fuzzy match (a source note with a similar but not identical source:): stop and ask which is correct. Never guess at ledger identity — a wrong guess either duplicates history or silently merges two distinct inputs.

4. Inspect

Findings come from the transcript, never from the chronicle. Where a session has a chronicle entry, read it for orientation if it helps — it is a serviceable map of a long transcript and will tell you where to look — but every finding is sourced from, and quoted from, the transcript itself. The reason is the whole point of the constraint, so it survives a rewrite: the chronicle is written for a human reader by the agent whose blind spots this graph exists to catch, and it has already compressed away the corrections, the dead ends, and the exact wording that an observation is made of. Extraction that runs on it inherits every one of those omissions and has no way to notice. The first real run of this pipeline found three divergences between a session's own account of itself and what its transcript showed; a reader of the account alone would have found none of them.

The one exception is the path named in step 2, where the transcript is verifiably gone. There the chronicle is what remains, extraction reads it, and the source note carries raw: unavailable so the graph shows it.

Read the resolved input and decide what it contains, sized to the input:

  • Under ~1,500 words: read it inline, in this conversation.
  • Above ~1,500 words: fan out one subagent per signal class (below), each returning a structured list of findings for its class. Synthesize the returned lists yourself before moving to the plan — don't forward subagent output verbatim.

A reader that returns nothing has not reported an empty class. Expect to have to ask: readers routinely finish their read and go idle without volunteering findings, so silence, an empty message, and a reader that simply stopped are all the same thing — no report yet. Follow up and get the findings. Only an explicit "nothing in this class" from that reader about that class lets you record it as empty. If a reader still won't report after a follow-up, the class failed — route it to step 8 rather than carrying it into the plan as zero findings. A run that reads silence as "nothing found" builds its plan from an empty set, stamps the source processed in step 9, and reports a clean sweep; since the stamp is what makes a re-run resume instead of redo, that session is marked handled for good. The failure looks exactly like success.

The signal classes this graph looks for in a session:

  • Design decisions + rationale. A choice was made and a reason given — becomes a decision attractor or evidence for an existing one. ("Reverted the validator; graph health rests on approval + conventions.")
  • Failed approaches / corrections. Something tried that didn't work, or a course-correction — feeds pattern evidence (often an anti-pattern crystallizing).
  • Recurring lessons / patterns. A lesson that shows up more than once — becomes an observation supporting a pattern attractor, or a new pattern once it has ≥1 piece of evidence.
  • Reflections / meta-trends. A higher-order observation about how the work itself is evolving — may seed a pattern or, stripped of specifics, a promotion candidate for the commons.

5. Propose the plan

Lay out, per note, what will be created and what will be updated — and just as importantly, what's being skipped and why (already captured, too operational, not durable — see the sibling knowledge-graph skill's conventions). Present it as [y / edit / explain]. One approval gates the entire run; there is no per-note confirmation after this point except the re-pause condition in step 6. The Todoist sink (step 7) and the promotion candidates (step 11) ride in this same plan, clearly marked.

The plan names the synthesis work. Say which it will be: linking the chronicle entry that already exists for this session, or invoking meta-claude:session-chronicle to write one. Both are ordinary — a session chronicled at the time and processed later arrives with its synthesis written; a session processed fresh does not. It belongs here because what the chronicle covers is one of the things being approved, and because a write that never appears in the plan is a write escaping the one gate this pipeline rests on.

The plan also names what step 2 already wrote — the source note, and the archived transcript or the inlined page. Preservation runs before this gate by design, so a plan that doesn't mention it leaves the reader unable to tell what has already landed and what this approval is actually deciding.

6. Run to completion

Execute the approved plan in dependency order — notes that other notes will link to get written first (an attractor before the evidence that cites it, unless the attractor already exists). Re-pause only for genuine ambiguity or a conflict with the existing record (the plan said one thing and the graph, on write, turns out to already say something incompatible). Creating a new entity, attractor, or note that simply doesn't exist yet is not ambiguity — proceed without pausing.

The chronicle is written here, as a sibling of extraction. Whatever step 5 proposed — linking the existing entry, or invoking meta-claude:session-chronicle to write one — happens now, alongside the observations rather than before or after them. Neither feeds the other: both are made from the same transcript. Link it from the source note when it lands, and do not extract from it once it exists.

Invoke meta-claude:session-chronicle; never reimplement it. It has its own rules about how it writes, what it asks, and in what order — including a reflection-ordering constraint that is its to enforce, not this skill's to second-guess. Call it and link what comes back.

For each finding from step 4: if it supports an existing pattern or decision, follow the sibling knowledge-graph skill's Capture workflow and its Extraction Workflow: session — write the observation under observations/, add it to the attractor's ## evidence section, and update that attractor's map-entry annotation in maps/patterns.md or maps/decisions.md if the "so what" shifted. If a finding names a plugin or skill not yet in entities/, add a lookup-only entity note and list it under the right heading in maps/entities.md. If a finding is a genuinely new generalization with no attractor yet, follow "Promoting a cluster to an attractor" rather than forcing a link — but per this graph's judgment bar (capture when unclear), it is fine to record an observation and flag it as unattached for a future run to cluster. A design choice with reasoning becomes (or feeds) a decision; a recurring truth becomes (or feeds) a pattern.

7. Sinks

Route any non-graph output (tasks) to the sink named for it, never as a note in the graph.

Tasks (Todoist). A finding that names a concrete, actionable follow-up — not a knowledge claim — routes here: a fix to make, an issue to file, a skill to sharpen. These are created with the td CLI in the claude code marketplace project. Approval mode is batch: the tasks are listed inside the step-5 plan and approved once, together with the notes — this orchestrator does not add a second confirmation. When creating them, always pass --labels next (or --labels waiting if the task is genuinely blocked on something external); a label-less task falls out of the GTD filters. Run td task add --help first if unsure of the flags. This sink runs after any note whose link it references exists — never against a stale or missing one.

8. Continue-and-collect

If a step in the approved plan fails, skip it and continue with the remaining independent steps rather than aborting the run. Collect every failure and report them together at the end, with enough detail to retry (what was attempted, what error came back), and offer to retry the failed steps now.

A signal class whose reader never reported belongs here too, even though it failed before the plan existed — that is the one failure with no error message attached, so a step that only collects throws will miss it. Name the class, say plainly that it was never inspected, and offer its re-read alongside the other retries. A class that drops out quietly between step 4 and this report is indistinguishable from a class that genuinely had nothing in it.

9. Stamp

Write or update the processed: stamp on the source note — a YAML list entry with date:, ran: (what was actually written), skipped: (what was deliberately not written, and why), and errored: (what failed, per step 8). It's a list so history accumulates across augment-mode runs rather than being overwritten.

Don't write the stamp while a fanned-out class is unaccounted for. If step 4 handed classes to subagents, every one must have either returned findings or explicitly reported none before this stamp goes on. While any is outstanding, withhold it, name the classes you never heard from, and offer the re-read. The stamp is what makes a re-run resume instead of redo, so stamping over an uninspected class doesn't lose it temporarily — it closes the source for good. This gate is about the fanout only: an inline read has no readers to hear from and inspected every class in this conversation, so it stamps normally.

10. Report

Summarize created / updated / skipped / errored, then append a changelog entry to knowledge/changelog.md following this graph's own conventions — see the "Session Protocol" section of the sibling knowledge-graph skill for the format. If the run surfaced something a skill outside the plan could act on, suggest it in the report rather than auto-running it.

Pending template patches belong in this report. This skill was generated from a plugin template and has diverged since — you own it. When the plugin fixes a defect in that template, nothing about the fix reaches this file on its own; the plugin's /graph-patch skill is what propagates it, and it only runs when someone invokes it.

You cannot tell whether anything is actually pending, and must not imply you can. What has already been applied here is recorded in .commons.yml under generated:, but what exists to apply lives in the plugin's delta log — and this skill has no path to the plugin. Any plugin path written into this file was resolved when the file was generated, so it names the version that generated it, not the one installed now. Suggest /graph-patch and let it answer; never read the log yourself, and never auto-run it, since it edits this prose under per-delta approval.

Withhold the suggestion on a cadence rather than raising it every run — a line that always appears carries no information:

  • No generated: block in .commons.yml — nothing has ever been propagated here. Raise it every run; the condition ends the first time /graph-patch runs.
  • A block exists — raise it about monthly. When you do, write the literal marker [patch-check-suggested] into this run's changelog entry, and before raising it again scan both knowledge/changelog.md and the most recent knowledge/changelog/YYYY-MM.md for that marker. Both halves matter: free-form prose isn't reliably recognizable to the run that has to find it, and this graph rotates its changelog monthly, so scanning only the live file would re-raise at every month boundary.
  • Can't tell — raise it. A redundant line costs a glance; a silent gap is what this exists to end.

A category with no type is a schema gap — report it. Separately from the notes this run wrote: did the session keep naming a kind of thing this graph has no entity type for? This graph declares plugin and skill. If a run keeps naming agents, hooks, or MCP servers as things a reader would look up, that isn't a missing note — it's a missing type, and no amount of note-writing fixes it. Ask this whether or not the graph already has an entity type: one that declared none is exactly the graph most likely to need its first.

The bar is recurrence, not one sighting. Several distinct instances of one category, each named as something you'd look up, none covered by plugin or skill — and existing notes in the graph can supply corroborating instances when a single run is thin. Name the category and list the instances evidencing it, so the reader judges from evidence rather than from your assertion.

Recommend; don't do. Where an entity: list already exists, the work is a new entry in .commons.yml, a directory, a map, and backfilling notes for the instances you found. Where there is none, the work is establishing the tier — the larger ask, and worth saying rather than folding in. Either way the sanctioned route is re-interviewing graph-init's block 2; .commons.yml belongs to graph-init, never to this skill.

Gate it the same way as pending patches. When you raise a category, write [entity-type-gap: <category>] into this run's changelog entry. Before raising one, scan both knowledge/changelog.md and the most recent knowledge/changelog/YYYY-MM.md for the literal prefix [entity-type-gap:, read the category names already recorded under it, and judge whether yours is the same gap by meaning — not by string match. Nothing holds the wording stable between runs: "MCP servers" one month and "MCP server integrations" the next are one gap already raised, and matching the whole marker literally would re-raise it every run while each run looked locally correct. Suppression is per-category, so a genuinely different gap still surfaces; it expires when the marker rotates out of both files, letting a still-recurring category earn a second mention.

11. The promotion tail

After the plan in step 5 is assembled — before presenting it — ask one more question of the run's findings: does any of this generalize beyond claude-code-plugins? Candidates go into the same plan, clearly marked as promotions, not into a separate pass someone has to remember to run.

Refresh the target before the screen runs. ~/Developer/commons is a shared graph other machines push to, and the screen below reads whatever this clone last saw. If it's a git repo with a remote, fetch and fast-forward first, then say what came in — new claims and graduated questions change the screen's answer, and a stale copy makes "not already steering" decorative. The check is that the fast-forward actually happened, not that the fetch returned: a fetch can exit cleanly while the fast-forward is refused because the local copy diverged or the tree is dirty, and that reads exactly like "already current." With no remote, no network, no git, a fetch error, or a refused fast-forward, run the screen anyway — but say plainly it ran against a possibly-stale copy, rather than letting silence imply the target is current.

The screen, applied to each candidate:

  • Strip the entities. Remove every name, date, and domain-specific noun (the plugin, the skill, the file path). Is there still a claim left, or was the "insight" just a fact about this instance?
  • Method over material. A reusable way of thinking generalizes; a fact about this repo's specific material usually doesn't.
  • Non-obvious. Would you tell your future self this, in a different domain, and have it be useful?
  • Not already steering. Check the target (~/Developer/commons, or ~/.claude/rules/ if promoting from the commons) for something that already says this before proposing it again.

When uncertain, propose anyway — a declined candidate costs one skip at plan review; a missed observation is invisible damage that never gets a second chance.

This domain, concretely. Craft principles tend to generalize out: human-in-the-loop refinement, spec-first, agent orchestration, prose-over-machinery — anything true of building good AI tooling in general, stated as method. What must never leave: this repo's file layout, the marketplace's structure, specific plugin versions or quirks, exact skill and agent names. If a lesson only holds because of these plugins' particular shape, it stays; if it would help a future self building something unrelated, it goes.

Approved promotion candidates are executed by invoking this plugin's promote skill once per candidate, after the rest of the plan has run — a promotion derives from a note that needs to exist first.

Repository
ddehart/claude-code-plugins
Last updated
First committed

Is this your skill?

If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.