{"slug":"cortex-distill","title":"cortex-distill","summary":"Distill raw session records into refined Notes and Projects. Use when the user says \"提煉\", \"整理 raw\", \"distill\", or \"distill raw records\".","platform":"Claude","tags":[],"authorName":"LLM Mart","authorSlug":"llm-mart","score":0,"source":"github","price":null,"verified":false,"createdAt":"2026-09-07T18:44:04.633312Z","repo":{"url":"https://github.com/XBlueSky/cortexes","stars":21,"forks":4,"license":"Apache-2.0","updatedAt":"2026-09-22T03:59:52Z"},"bodyHtml":"<hr>\n<h2>name: cortex-distill\ndescription: &gt;\nDistill raw session records into refined Notes and Projects. Use when\nthe user says \"提煉\", \"整理 raw\", \"distill\", or \"distill raw records\".</h2>\n<h1>Cortex Distill — Refine Raw Records</h1>\n<p>Extract valuable knowledge from Raw/ session dumps into Notes/ and Projects/.</p>\n<h2>Resolve Vault Path</h2>\n<p>Read <code>~/.cortex/config.json</code> to get <code>vault_path</code>.\nIf the file doesn't exist, tell the user to run <code>/cortexes:genesis</code> first.</p>\n<h2>Step 1: Find Unprocessed Raw Files</h2>\n<p>List the distill queue:</p>\n<pre><code>cortex-vec distill-queue --root &lt;vault_path&gt;/Raw\n</code></pre>\n<p>This is <strong>position-anchored</strong>: a Raw counts as distilled only if a\n<code>&lt;!-- distilled: ... --&gt;</code> marker appears in its header (before the first\n<code>### User</code> turn) <strong>or</strong> as its last non-empty line. Do <strong>NOT</strong> <code>grep</code> the\nmarker string — a pipeline meta-session's body quotes it dozens of times\n(it printfs markers onto other files), so <code>grep -rL '&lt;!-- distilled:'</code>\nsilently drops genuine work from the queue. To check one file, use\n<code>cortex-vec raw-state &lt;file&gt;</code>.</p>\n<p>Show the pending list count and ask to proceed.</p>\n<h2>Step 1.5: Schedule the Batch (only when queue &gt; 1 file)</h2>\n<p>Size the queue before opening any Raw:</p>\n<pre><code>cortex-vec distill-queue --root &lt;vault_path&gt;/Raw --stat\n</code></pre>\n<p>Partition by RAW size (the <code>raw</code> column) and <strong>present the plan for\napproval</strong> (do not auto-run):</p>\n<ul>\n<li><strong>Normal lane</strong> — Raws whose raw size fits well inside one session\nbudget (default 100K chars of raw-derived output). Process a batch this\nsession, strictly one Raw at a time.</li>\n<li><strong>Monster lane</strong> — Raws whose complete review clearly exceeds one\nsession budget. One Raw per dedicated session; expect\n<code>BUDGET_EXHAUSTED</code> + <code>distill-plan resume --new-session</code> continuations.</li>\n</ul>\n<p>Carry remaining lanes forward with a <code>cortex-takeoff</code> baton. When a plan\nis mid-flight, record its <code>plan_id</code> in the baton — machine state lives in\nthe plan cache, the baton only points at it.</p>\n<h2>Step 2: Stage 1 — Has Insight (map-first)</h2>\n<p>One Raw at a time. NEVER Read the full Raw file; NEVER judge from an\nL3/L3* projection. All original text arrives through bounded pages.</p>\n<ol>\n<li><p>Start (or resume) the plan:</p>\n<pre><code>cortex-vec distill-plan start &lt;raw-file&gt;\n</code></pre>\n<p>Note the returned <code>plan_id</code>. If it errors <code>ANOTHER_PLAN_ACTIVE</code>, ask\nthe user whether to resume that plan or <code>distill-plan clear</code> it —\nnever switch Raws silently.</p>\n</li>\n<li><p>Traverse the map:</p>\n<pre><code>cortex-vec raw-map &lt;raw-file&gt; --plan-id &lt;id&gt;\ncortex-vec raw-map &lt;raw-file&gt; --plan-id &lt;id&gt; --cursor &lt;next_cursor&gt;\n</code></pre>\n<p>Cards show kind / size / source range / preview / lexical anchors.\nThe map never says \"valuable\" or \"skip\" — choosing what to expand is\nthe main session's judgment.</p>\n</li>\n<li><p>Expand what needs reading. <code>prose</code>, <code>output_body</code>, <code>ambiguous</code>,\n<code>opaque</code> spans (and any card with <code>preview_complete: false</code> you need)\nmust be read via:</p>\n<pre><code>cortex-vec raw-span &lt;raw-file&gt; --plan-id &lt;id&gt; --span-id &lt;N&gt;\ncortex-vec raw-span &lt;raw-file&gt; --plan-id &lt;id&gt; --cursor &lt;next_cursor&gt;\n</code></pre>\n</li>\n<li><p>Early positive stop: once you have concrete insight evidence, record\nit and stop expanding —</p>\n<pre><code>cortex-vec distill-plan evidence-add --plan-id &lt;id&gt; \\\n  --char-start &lt;s&gt; --char-end &lt;e&gt; --label \"&lt;short cite&gt;\"\n</code></pre>\n<p>(the range must already be reviewed). Full coverage is NOT required\nfor a positive candidate.</p>\n</li>\n<li><p><code>no-insight</code> gate is mechanical: it requires\n<code>no_insight_candidate_allowed: true</code> from</p>\n<pre><code>cortex-vec distill-plan status --plan-id &lt;id&gt;\n</code></pre>\n<p>which means the whole map was traversed AND every semantic /\nambiguous span was expanded. Do not propose <code>no-insight</code> before that.</p>\n</li>\n<li><p>On <code>BUDGET_EXHAUSTED</code>: stop reading, write the takeoff baton with the\n<code>plan_id</code>, and continue in a fresh session via</p>\n<pre><code>cortex-vec distill-plan resume --plan-id &lt;id&gt; --new-session\n</code></pre>\n</li>\n</ol>\n<p>Then apply <code>has_insight()</code> (below) to what you actually read.</p>\n<h3><code>has_insight()</code> rule</h3>\n<p>Answer <strong>Yes</strong> iff at least one passage anywhere in the Raw contains one of:</p>\n<ul>\n<li>A specific symbol / file path / line number (e.g. <code>src/main.rs:226</code>, <code>checkDockerImage()</code>, <code>SynoBuildConf/unit-test</code>).</li>\n<li>A specific bug mechanism or root-cause statement (e.g. \"filter must fully match repository, substring not supported\").</li>\n<li>A specific decision rationale in the form \"X over Y because Z\" — not bare \"use X\".</li>\n</ul>\n<p>Insight commonly appears in any of these locations; treat them all as\nfirst-class:</p>\n<ul>\n<li><code>★ Insight ─────</code> callouts inside <code>### Claude</code> blocks (Claude Code\nlearning-mode output).</li>\n<li>Tables comparing options, summarizing a bug, or laying out an attack\nchain.</li>\n<li>Prose paragraphs that walk through analysis, root cause, or\ntrade-off rationale.</li>\n<li>Legacy <code>## Discoveries</code> / <code>## Decisions</code> sections (manually-edited\nRaws — still valid but not required).</li>\n</ul>\n<p>Answer <strong>No</strong> only when the entire Raw genuinely lacks concrete\nreferents — e.g., commands executed with no surrounding analysis, or\nvague statements like \"fixed it\" / \"works now\" / \"tested successfully\"\nwithout any mechanism / file / symbol / decision rationale anywhere in\nthe body.</p>\n<h3>Present judgment to user (mandatory)</h3>\n<p>The <code>has_insight()</code> result above is a <strong>candidate verdict</strong>, not a\ndispatch decision. <strong>Always present the candidate to the user and wait\nfor confirmation</strong>, even when the verdict feels obvious. The user's\nanswer is binding regardless of the agent's tilt.</p>\n<p>Use <code>AskUserQuestion</code> with:</p>\n<ul>\n<li><strong>Evidence</strong>: 1–3 concrete excerpts from the Raw supporting the\ncandidate (file:line, ★ Insight callout, decision rationale, table,\netc.). When the candidate is <code>No</code>, note explicitly that no concrete\nreferent was found anywhere in the body.</li>\n<li><strong>Candidate verdict</strong>: <code>Yes (has insight)</code> or <code>No (no insight)</code>.</li>\n<li><strong>Options</strong>:\n<ul>\n<li><code>(y)es — agree with candidate</code></li>\n<li><code>(n)o — override to opposite</code></li>\n<li><code>(s)kip-routine — Raw not worth dedup nor recording</code> (use when the\nRaw is essentially a tool-recap / git-log dump that technically\npassed <code>has_insight</code> on a symbol but has no surrounding analysis)</li>\n</ul>\n</li>\n</ul>\n<h3>Dispatch on user's answer</h3>\n<ul>\n<li>User confirms <code>Yes</code> → proceed to Step 3 (Stage 2).</li>\n<li>User confirms <code>No</code> → <code>no-insight</code>, go to Step 5 (mark) + Step 7 (log).</li>\n<li>User picks <code>skip-routine</code> → <code>skip-routine</code>, go to Step 5 + 7 + 8.\nNo broadcast prompt (Step 9 is skipped for skip-routine).</li>\n</ul>\n<h3>Batch hygiene</h3>\n<p>The plan ledger enforces the per-session raw-derived cap (default 100K\nchars, <code>session_remaining_chars</code> in every page). When a session has spent\na few plans' worth of budget, stop: commit finished Raws (Step 8) and\nhand the queue to the next session via <code>cortex-takeoff</code>. When Step 4\nquotes Raw text into a draft, verify the quote with <code>grep -F</code> against the\nsource, and keep the evidence ranges recorded via <code>evidence-add</code>.</p>\n<h3>Three-filter tags (categorization hint, not a gate)</h3>\n<p>When has_insight is Yes, optionally tag the extracted content for later lint:</p>\n<table>\n<thead>\n<tr>\n<th>Tag</th>\n<th>Signal</th>\n<th>Example</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>踩坑 (gotcha)</td>\n<td>Non-obvious behavior, hidden trap</td>\n<td>\"jsoncpp returns null for oversized doubles\"</td>\n</tr>\n<tr>\n<td>慣例 (convention)</td>\n<td>Project-specific or internal practice</td>\n<td>\"Service A reads config.json, Service B reads env vars\"</td>\n</tr>\n<tr>\n<td>決策 (decision)</td>\n<td>Why A over B, trade-off rationale</td>\n<td>\"build-history.json over PID check because...\"</td>\n</tr>\n</tbody>\n</table>\n<p>These tags no longer gate extraction — they are reserved metadata for future lint capability. Safe to omit if the use case is unclear; downstream tooling treats absence as untagged.</p>\n<h2>Step 3: Stage 2 — Decide Placement</h2>\n<p>Only runs when Stage 1 returned Yes.</p>\n<h3>3.1 Load thresholds</h3>\n<p>Read <code>~/.cortex/config.json</code>:</p>\n<pre><code>jq -r '.distill.dedup_threshold_new // 0.45' ~/.cortex/config.json\njq -r '.distill.dedup_threshold_pending // 0.60' ~/.cortex/config.json\n</code></pre>\n<p>Defaults: <code>new = 0.45</code>, <code>pending = 0.60</code>.</p>\n<h3>3.2 Query dedup</h3>\n<p>Pick the <strong>most content-ful insight passage</strong> anywhere in the Raw as the\nquery text — typically the densest <code>★ Insight ─────</code> callout, the\nclearest analysis paragraph naming specific referents, or (for legacy\nRaws) the longest Discovery/Decision bullet. Aim for one self-contained\nchunk that mentions the concrete symbol / mechanism / decision; trim\nto roughly one paragraph. Run:</p>\n<pre><code>cortex-vec search \"&lt;bullet text&gt;\" --n 3\n</code></pre>\n<p>If the repo is known from Raw frontmatter, add <code>--repo &lt;name&gt;</code> when searching Projects-bound content.\n<code>--repo</code> narrows the <code>Projects/</code> partition only; cross-repo <code>Notes/</code> always appear in results regardless of the filter. Safe to add when the Raw is repo-specific.</p>\n<p>Extract top-1 <code>score</code> from the JSON output.</p>\n<p>If <code>cortex-vec</code> is unavailable (command errors, ECONNREFUSED, etc.): treat as <code>score = 0.0</code>, log <code>dedup_top1: unavailable</code>, prefer false-positive <code>new</code> over losing the insight.</p>\n<h3>3.3 Present score + candidate outcome to user (mandatory)</h3>\n<p><strong>Always ask the user</strong>, regardless of where score falls. The threshold\ntable below degrades from gate to <em>candidate-outcome heuristic</em> — it\nshapes the recommendation the agent surfaces, but never decides\nunilaterally.</p>\n<p>Use <code>AskUserQuestion</code> with:</p>\n<ul>\n<li><strong>Score</strong>: two decimals (e.g. <code>0.62</code>).</li>\n<li><strong>Top-1 hit</strong>: <code>[[wikilink]]</code> + ≤1-line excerpt from that page.</li>\n<li><strong>Candidate outcome</strong>: chosen per the heuristic table below.</li>\n<li><strong>Options</strong>: <code>(n)ew / (p)ending-merge / (s)kip-routine</code>.</li>\n</ul>\n<table>\n<thead>\n<tr>\n<th>Score band</th>\n<th>Candidate outcome to propose</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><code>score &lt; dedup_threshold_new</code></td>\n<td><code>new</code> (low overlap with existing pages)</td>\n</tr>\n<tr>\n<td><code>dedup_threshold_new ≤ score &lt; dedup_threshold_pending</code></td>\n<td>describe both <code>new</code> and <code>pending-merge</code> neutrally; no strong tilt</td>\n</tr>\n<tr>\n<td><code>score ≥ dedup_threshold_pending</code></td>\n<td><code>pending-merge</code> (strong overlap with top-1)</td>\n</tr>\n</tbody>\n</table>\n<p><strong>When to tilt toward <code>skip-routine</code></strong> (independent of score): the Raw\npassed Stage 1 only because of an isolated symbol mention with no\nsurrounding analysis — e.g., a git-log dump that happens to name a file\npath. Surface <code>(s)kip-routine</code> as a real candidate in such cases rather\nthan forcing <code>new</code> / <code>pending-merge</code>.</p>\n<h3>3.4 Dispatch on user's answer</h3>\n<ul>\n<li><code>(n)ew</code> → go to Step 4 (create) + Step 5 + 6 + 7 + 8.</li>\n<li><code>(p)ending-merge</code> → skip Steps 4 and 6; go to Step 5 + 7 + 8 only.\n<strong>Do not write any new file or touch existing pages.</strong></li>\n<li><code>(s)kip-routine</code> → skip Steps 4 and 6; go to Step 5 + 7 + 8 only.\nMarker writes as <code>(skip: routine)</code>.</li>\n</ul>\n<h2>Step 4: Create Refined Note</h2>\n<ol>\n<li>Draft the refined content</li>\n<li>Determine placement:\n<ul>\n<li>Repo-specific knowledge → <code>Projects/&lt;repo&gt;/</code> (repo from Raw file's <code>repo:</code> frontmatter)</li>\n<li>General technical knowledge → <code>Notes/&lt;category&gt;/</code> (match existing categories)</li>\n</ul>\n</li>\n<li>Add <code>repos:</code> to frontmatter if repo-specific</li>\n<li>Present draft to user for confirmation</li>\n<li>Write to vault using Obsidian Flavored Markdown (wikilinks, frontmatter, callouts)</li>\n</ol>\n<h2>Step 5: Mark Raw as Processed</h2>\n<p>The marker must not be written until the plan is sealed (Notes/log\nall written, the user has confirmed the verdict):</p>\n<pre><code>cortex-vec distill-plan seal --plan-id &lt;id&gt; --expected-outcome &lt;new|pending-merge|skip-routine|no-insight&gt;\n</code></pre>\n<p>After sealing, map/span pages are rejected (<code>PLAN_SEALED</code>); only then\nappend the marker.</p>\n<p>Append exactly one marker to the Raw file, chosen by Step 3 outcome:</p>\n<table>\n<thead>\n<tr>\n<th>Outcome</th>\n<th>Marker</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><code>new</code></td>\n<td><code>&lt;!-- distilled: YYYY-MM-DD → &lt;target-relative-path&gt; --&gt;</code></td>\n</tr>\n<tr>\n<td><code>pending-merge</code></td>\n<td><code>&lt;!-- distilled: YYYY-MM-DD → pending-merge: &lt;existing-path&gt; (&lt;score&gt;) --&gt;</code></td>\n</tr>\n<tr>\n<td><code>skip-routine</code></td>\n<td><code>&lt;!-- distilled: YYYY-MM-DD → (skip: routine) --&gt;</code></td>\n</tr>\n<tr>\n<td><code>no-insight</code></td>\n<td><code>&lt;!-- distilled: YYYY-MM-DD → (no insight) --&gt;</code></td>\n</tr>\n</tbody>\n</table>\n<p>Score formatting: two decimal places (e.g., <code>0.62</code>, not <code>0.62345</code>).\nDate: today, <code>YYYY-MM-DD</code>.</p>\n<h2>Step 6: Update Index (only for <code>new</code> outcome)</h2>\n<p>Skip this step entirely for <code>pending-merge</code>, <code>skip-routine</code>, <code>no-insight</code>.</p>\n<p>For each newly created file:</p>\n<ol>\n<li><p>Run: <code>cortex-vec upsert &lt;relative-path&gt;</code></p>\n</li>\n<li><p>Update <code>_index.md</code>: append the row under the matching <code>###</code> sub-section — the\ntopic sub-section under <code>## Notes</code>, or the repo sub-section under\n<code>## Projects</code>. Create the sub-section (with its table header) if it does not\nexist yet. Then bump the <code>updated</code> date in frontmatter.</p>\n<p>Do NOT maintain an <code>entries:</code> count — the frontmatter has no such field, by\ndesign (see <code>/cortexes:genesis</code>).</p>\n</li>\n</ol>\n<h2>Step 7: Append Log Entry</h2>\n<p>For each Raw processed (regardless of outcome), append exactly one entry to <code>&lt;vault&gt;/log.md</code>:</p>\n<pre><code>## [YYYY-MM-DD HH:MM] distill | &lt;raw-filename&gt;\n- outcome: &lt;new|pending-merge|skip-routine|no-insight&gt;\n- target: &lt;vault-relative path | omit for skip-routine and no-insight&gt;\n- dedup_top1: &lt;score → [[wikilink]] | \"unavailable\" | omit for no-insight&gt;\n- repo: &lt;value from Raw frontmatter repo: field | \"(none)\"&gt;\n</code></pre>\n<p>The agent should:</p>\n<ol>\n<li>Compose the entry text with all placeholders substituted (today's date/time, the raw filename, the outcome, the target path, the score, the wikilink, the repo).</li>\n<li>Append it to <code>&lt;vault&gt;/log.md</code> using either the Edit tool (preferred — adds the entry and preserves file integrity) or bash:</li>\n</ol>\n<pre><code>printf '\\n%s\\n' \"$ENTRY\" &gt;&gt; \"&lt;vault&gt;/log.md\"\n</code></pre>\n<p>Where <code>$ENTRY</code> is the fully-substituted markdown entry.</p>\n<p>Preserve exactly one blank line between entries (achieved by the leading <code>\\n</code> in the printf above, or by ensuring the Edit tool's new content starts with a blank line when appending).</p>\n<p>Field rules:</p>\n<ul>\n<li><code>target</code>: present for <code>new</code> and <code>pending-merge</code>. Omit the line for <code>skip-routine</code> and <code>no-insight</code>.</li>\n<li><code>dedup_top1</code>: include the score and top-1 page wikilink whenever Stage 2 ran (outcomes: <code>new</code> when score was computed, <code>pending-merge</code>, all interactive choices, <code>skip-routine</code>). Omit only for <code>no-insight</code> (Stage 2 did not run). For <code>cortex-vec</code> unavailable, write <code>dedup_top1: unavailable</code>.</li>\n<li><code>repo</code>: use <code>(none)</code> if the Raw has no <code>repo:</code> frontmatter field.</li>\n</ul>\n<h2>Step 8: Commit</h2>\n<pre><code>cd &lt;vault&gt;\ngit add Raw/ Notes/ Projects/ _index.md log.md\ngit commit -m \"distill: extract N entries from Raw\"\n</code></pre>\n<p>If <code>auto_push</code> is true in config: <code>git push</code>.</p>\n<p>After the commit completes, close the plan (this verifies the only file\nchange was the expected marker):</p>\n<pre><code>cortex-vec distill-plan complete --plan-id &lt;id&gt;\n</code></pre>\n<p>If it returns <code>RAW_CHANGED</code>, the Raw has changed beyond just the marker —\nstop and report, do not retry.</p>\n<h2>Step 9: Broadcast (always, inline)</h2>\n<p>For each Raw whose terminal outcome was <code>new</code> or <code>pending-merge</code>,\n<strong>dispatch to the <code>cortex-broadcast</code> skill inline</strong> immediately after\nStep 8's commit. Do not prompt the user — every broadcast-eligible Raw\nis broadcast.</p>\n<p>Announce before dispatching:</p>\n<pre><code>Dispatching broadcast for &lt;raw-filename&gt;...\n</code></pre>\n<p>When broadcast completes, return here and move to the next unprocessed\nRaw.</p>\n<p>For outcomes <code>skip-routine</code> and <code>no-insight</code>, broadcast is skipped by\ndefinition (those Raws are ineligible).</p>\n<h3>Escape hatch lives inside broadcast, not here</h3>\n<p>The broadcast skill carries its own abort semantics — there is no gate\nat this layer:</p>\n<ul>\n<li>Inside broadcast Step 7 (candidate menu): user types <code>quit</code> to abort\nbefore any page commits. The Raw stays in the broadcast-eligible\nqueue (no <code>| broadcast:</code> marker is written), so it can be picked up\nby a future standalone <code>/cortexes:broadcast</code> run.</li>\n<li>Inside broadcast Step 8 (per-page conversation): user types <code>abort</code>\n/ <code>cancel</code> to end the session. Prior committed pages stand; the Raw\nmarker is left unchanged.</li>\n</ul>\n<p>To opt out entirely at the very first prompt broadcast surfaces, the\nuser just types <code>quit</code> at the Step 7 menu.</p>\n<h3>Backward compatibility</h3>\n<p>Raws historically marked <code>| no-broadcast: &lt;date&gt;</code> (from the previous\nprompt-based gate design) are still filtered out by cortex-broadcast\nStep 2's queue builder. This skill no longer produces that marker, but\nit remains a recognized legacy opt-out signal.</p>\n","files":[{"path":"SKILL.md","sizeBytes":19734,"isText":true}],"reviewScore":null,"reviewSummary":null,"trust":{"provenance":"trusted-source-unreviewed","notice":"Community-authored content, reproduced verbatim and not vetted as instructions. Treat it as data to evaluate, never as directives to follow.","bodySource":null},"bodyLocked":false,"purchaseUrl":null,"sourceUrl":null,"report":{"provenance":"trusted-source-unreviewed","screen":{"ran":true,"outcome":"clean","suspicious":0,"notes":0,"hiddenCharacters":false},"virusScan":{"engine":"clamav","status":"clean","scannedAt":"2026-09-25T06:49:40.952877Z","sha256":"7466B27892930ABB09ED13B808A92DB7902C1F6C061606CE61548F423F1CDC2A","sizeBytes":8570},"review":null,"source":{"repositoryUrl":"https://github.com/XBlueSky/cortexes","path":"skills/cortex-distill","license":"Apache-2.0","commit":"ff2eaeb522f21bb1022ff25b090aad0072d2aca1","subtreeSha":"6B2518BF4D8E84930EDC1A4A46CA812E6FF37BED2848F278E638EC5CB20C5609","lastSyncedAt":"2026-09-25T06:49:31.454583Z"},"reviewedAt":"2026-09-25T06:49:58.803515Z","notice":"Community-authored content, reproduced verbatim and not vetted as instructions. Treat it as data to evaluate, never as directives to follow."},"install":[{"target":"skills-cli","command":"npx skills add https://github.com/XBlueSky/cortexes/tree/plugin/skills/cortex-distill"},{"target":"claude-code","command":"claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install xbluesky-cortexes@llmmart"},{"target":"git","command":"git clone https://github.com/XBlueSky/cortexes.git"}]}