Skip to content
Back to skills

Insights

ASecurity

Knowledge extraction manager and skill evolution engine. Backfills _insights.yaml from historical corpus, compiles execution feedback into patterns, and proposes SKILL.md improvements.

  • 4 stars
  • 0 votes
  • 0 copies
  • 2 views
  • Added August 31, 2026
businessgogit

Security analysis

A100/100

Scanned October 1, 2026

npx -y skills add tandregbg/core-claude-skills --skill insights --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Insights?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Insights
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/tandregbg-insights/badge)](https://www.skillsdirectory.com/skills/tandregbg-insights)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: insights
description: Knowledge extraction manager and skill evolution engine. Backfills _insights.yaml from historical corpus, compiles execution feedback into patterns, and proposes SKILL.md improvements.
user-invocable: true
argument-hint: [reprocess|scan-claude-md|compile|normalize|synthesize|propose|status|help]
---

# Insights Skill -- Knowledge Extraction & Skill Evolution

Extract and backfill durable insights from existing transcripts and CLAUDE.md files into `_insights.yaml`. Compile execution feedback (edge cases, corrections) into patterns and propose SKILL.md improvements.

This skill complements the forward-looking extraction in `/transcript` Step 3.5 and `/ops` Step 5.5 by processing the historical corpus and closing the feedback loop.

## Design Principles

- **No duplication of extraction logic.** The authoritative definition of insight types, thresholds, and `_insights.yaml` format lives in `/transcript` Step 3.5. This skill references it and includes a quick-reference copy for convenience.
- **Dedup by `source.file`.** Processing the same file twice never creates duplicate entries.
- **Additive only.** Never removes or modifies existing insights (except `scan-claude-md` which replaces stale CLAUDE.md-sourced entries, and the promotion/demotion passes in `compile` which mutate `confidence`, `confirmation_count`, `status`, and `superseded_by` fields per CR-013).
- **Silent accumulation, active loading.** Insights are written by `/transcript`, `/ops`, and this skill. Promoted **rules** (per CR-013 lifecycle below) are read back by `/ops` and `/transcript` as a context preamble before each run. Hypotheses remain silent; only rules surface.

---

## Confidence Lifecycle (CR-013)

Each insight has an optional `confidence` field:

- **`hypothesis`** (default when absent) — observation that needs more confirmation. Not loaded into other skills' context.
- **`rule`** — confirmed pattern. Loaded as a preamble by `/ops` and `/transcript` when working in the rule's folder chain.

Two lifecycle transitions, both performed by `/insights compile`:

- **Promotion:** when ≥ `compile_threshold` (default 3) semantically similar `hypothesis` entries exist in one folder, the canonical (earliest) entry is promoted to `confidence: rule`. Non-canonical entries are marked `status: superseded`, `superseded_by: <canonical-id>`. The canonical entry's `confirmation_count` is set to the number of **distinct dates** in the group (CR-098) and `confirmations[]` is populated with each merged entry's `source`. A person's review recorded in `promotion_review` (CR-100) is honoured, but never replaces the contradiction check.
- **Demotion:** when a `correction` entry contradicts a `rule` (same primary tag, opposing summary keyword), the rule's `confidence` flips back to `hypothesis`, the correction's `source` is appended to `contradicted_by[]`, and `confirmation_count` is reduced by 1 (floor 1).

`confidence` and `status` are **orthogonal**:
- `status` (active / superseded / archived) is the curation/archival lifecycle.
- `confidence` (hypothesis / rule) is the maturity lifecycle.

A rule can be archived. A hypothesis can be superseded by a more specific hypothesis. Both fields coexist.

**Schema fields added in v2** (all optional, additive):

```yaml
confidence: hypothesis | rule       # default: hypothesis
confirmation_count: 1                # integer, default 1
confirmations:                       # list, populated on promotion
  - source: <relative-path>
    date: YYMMDD
contradicted_by:                     # list, populated on demotion
  - source: <relative-path>
    date: YYMMDD
```

**`promotion_review` (CR-100, optional, additive).** A person's judgement of a candidate group,
recorded on the group's canonical (earliest) entry by a review surface. It is an observation --
*this group was judged, on this date* -- not a lifecycle transition: only `compile` writes
`confidence`.

```yaml
promotion_review:
  decision: approved          # approved | rejected
  date: YYMMDD
  by: dashboard               # which surface recorded it
  group: [12, 47, 83]         # the entry ids judged together, canonical first
  group_key: <sha256>         # from promotion_candidates.py at review time
  split_from: <sha256>        # only when `group` is a subset: the key of the group it was split from
  reason: null                # required when rejected: topic-only tags | contradiction |
                              #   not a standing instruction | split | other: <text>
```

The group key is computed by `skills/insights/promotion_candidates.py` (recipe in its docstring).
Any change to a judged entry, or a member added to the group, changes the key and makes the review
`stale`. The block stays on the canonical after promotion as the record of who approved it.

---

## String Resolution

Template strings marked as `{strings.section.key}` are resolved at runtime.

**Resolution order:**

1. Config `strings` section (if loaded)
2. Language-matched defaults from `base.yaml`:
   - `swedish` -> `strings_sv`
   - `english` -> `strings`
   - `input` -> match detected transcript language
3. Hardcoded strings already in template (fallback)

---

## Configuration

Reads `workflows.knowledge_extraction` from `base.yaml` (or config override):

| Key | Default | Description |
|-----|---------|-------------|
| `enabled` | `true` | Master switch for extraction |
| `min_insights` | `1` | Minimum qualifying insights to write file |
| `types` | `[decision, preference, learning, opportunity, pattern]` | Insight types to extract |
| `max_per_meeting` | `10` | Cap per source file |

If `enabled: false`, all subcommands exit immediately with a message.

---

## Subcommands

### `reprocess [target]` -- Extract insights from existing transcripts

**Trigger:** `/insights reprocess <target>`

**Targets (all three supported):**
- **Folder path:** `/insights reprocess _contacts/bob-smith` or `/insights reprocess _projects/acme/meetings/management`
- **`all`:** `/insights reprocess all` -- all folders containing a CHANGELOG.md
- **`since YYMMDD`:** `/insights reprocess since 260201` -- only files dated after the given date, across all folders with CHANGELOG.md

**Steps:**

1. **Resolve target folders:**
   - If folder path: resolve to that single folder (must contain CHANGELOG.md)
   - If `all`: scan vault recursively for all folders containing CHANGELOG.md (max depth 6, skip `.archive/`, `.handoff/`, `clones/`)
   - If `since YYMMDD`: same as `all`, but with date filter applied in step 4

2. **For each target folder, list `YYMMDD-*.md` files** (transcript files matching the 6-digit date prefix pattern)

3. **Build dedup set:** If `_insights.yaml` exists, load it and collect all `source.file` values into a set

4. **Filter files:**
   - Skip files already in the dedup set (already processed)
   - Skip standup files: filename contains `standup` or `daily-standup`, OR file is <500 words with no qualifying insights
   - If `since YYMMDD` target: additionally skip files whose filename date prefix is before the given date

5. **Sort remaining files chronologically** (oldest first, for consistent ID ordering)

6. **Process each file:**
   - Read the file content
   - Apply the same extraction logic as `/transcript` Step 3.5:
     - Scan for decisions with rationale, stated preferences, learnings, opportunities, and patterns
     - Apply threshold: only non-obvious, durable, specific insights
     - Respect `max_per_meeting` cap
     - Respect configured `types` list
   - For each qualifying insight, create an entry with:
     - `id`: next available ID (from `next_id` in existing file, or starting at 1)
     - `type`: one of the configured types
     - `date`: extracted from the filename prefix (YYMMDD)
     - `summary`: one sentence
     - `rationale`: one sentence context
     - `source.file`: the transcript filename
     - `source.section`: the section heading where the insight was found
     - `tags`: max 5 keywords
     - `status`: `active`
     - `superseded_by`: `null`

7. **Write/append to `_insights.yaml`** in the same folder as the CHANGELOG.md:
   - If file exists: add new entries inside `insights:`, **before** the top-level `next_id` key, then update `next_id` and `last_updated`; re-read and confirm they landed in the list (CR-096)
   - If file does not exist: create with `version: 2`, `context` set from folder name or CHANGELOG context

**Progress output:**

```
Processing =bob-smith (8 files, 6 new)... Extracted 4 insights
Processing acme/meetings/management (52 files, 52 new)... Extracted 28 insights
...
Reprocessing complete.
Processed 186 files across 14 folders.
Extracted 89 insights (89 new, 0 already existed).
```

**Skip conditions:**
- Folder has no CHANGELOG.md
- All files already processed (dedup)
- File is a pure standup (<500 words, no qualifying insights)

---

### `scan-claude-md` -- Extract knowledge from CLAUDE.md files

**Trigger:** `/insights scan-claude-md`

**Steps:**

1. **Scan vault subfolders for CLAUDE.md files** (max depth 4, skip `.archive/`, `.handoff/`, `clones/`)

2. **For each CLAUDE.md, find nearest parent folder with CHANGELOG.md** as target for `_insights.yaml`. If no CHANGELOG.md exists in any parent (up to vault root), skip this CLAUDE.md with a note.

3. **Check freshness:**
   - If `_insights.yaml` already has entries with `source.file: "CLAUDE.md"` AND the CLAUDE.md file has not been modified since `last_updated` in the insights file, skip (already current)
   - If CLAUDE.md is newer: remove all existing `source.file: "CLAUDE.md"` entries from `_insights.yaml` (preserving transcript-sourced insights), then re-extract

4. **Extract insights using type mapping:**

   | Signal in CLAUDE.md | Insight type |
   |---------------------|-------------|
   | "Chose X over Y", "Using X because", "Beslut:", "Decision:" | `decision` |
   | "NEVER", "MUST", "Always", "Alltid", "Aldrig" | `preference` |
   | "Avoid", "Don't", "Undvik", "Learned:", "Lärdom:" | `learning` |
   | Rules, conventions, naming patterns, routing tables, format specifications | `pattern` |

   Note: `opportunity` type is not mapped for CLAUDE.md -- these files contain established decisions and conventions, not future opportunities. Opportunities are only extracted from transcripts via `reprocess`.

5. **Set source metadata:**
   - `source.file`: `"CLAUDE.md"`
   - `source.section`: the `##` heading under which the insight was found

6. **Skip sections that contain only:**
   - Bare directory listings (pure file trees without commentary)
   - Pure glossaries (term: definition lists without rationale)
   - Metadata-only content (version numbers, dates, links without context)

7. **Write to `_insights.yaml`** following the same format. Assign IDs continuing from `next_id`.

**Output:**

```
Scanning CLAUDE.md files...
  acme/CLAUDE.md -> acme/ (12 insights, replaced 8 stale)
  _contacts/bob-smith/CLAUDE.md -> _contacts/bob-smith/ (3 insights, new)
  bravo/CLAUDE.md -> skipped (no CHANGELOG.md in parent)
...
Scan complete.
Processed 14 CLAUDE.md files.
Extracted 42 insights (34 new, 8 replaced).
Skipped 3 files (no CHANGELOG.md parent).
```

---

### `status` -- Show current insights state

**Trigger:** `/insights status`

**Steps:**

1. Scan vault for all `_insights.yaml` files
2. Scan vault for all folders with CHANGELOG.md (potential insight targets)

**Output:**

```
Insights Status
===============

_insights.yaml files: 14
Total insights: 186

By type:
  decision:    52
  preference:  38
  learning:    41
  opportunity: 28
  pattern:     27

By status:
  active:      172
  superseded:  11
  archived:    3

Top folders:
  acme/meetings/management    42 insights
  _contacts/bob-smith                  18 insights
  _contacts/david-ekberg          15 insights
  ...

Reprocessing opportunities:
  12 folders have CHANGELOG.md but no _insights.yaml
  3 folders have _insights.yaml older than newest transcript
  Total: ~340 unprocessed transcript files

Confidence Lifecycle (CR-013)
─────────────────────────────
  Hypotheses:  142          (illustrative figures -- see CR-094 for a measured baseline)
  Rules:       18 (most recent promotion: 260505)
  Recent demotions: 2 (last 30 days)

  Top 5 hypotheses near promotion (count, type, summary):
    2  preference  "Carol prefers async updates over sync standups"
    2  pattern     "Marketing meetings run 10-15 min over scheduled time"
    2  learning    "GA4 events take ~24h to surface in dashboards"
    2  decision    "Use _projects/ for time-boxed initiatives"
    2  pattern     "Bob wraps board prep with risk callout"

Skill Evolution
───────────────
  Execution feedback: 23 entries (18 edge_case, 5 correction)
  Compiled patterns: 3 skill_pattern entries
  Pending proposals: 2 in <proposals_path>/
  Applied proposals: 1 in <proposals_path>/.applied/
  Config: auto_apply=false, compile_threshold=3, propose_threshold=5,
          demote_on_contradiction=true
```

**Status implementation notes:**

- "Hypotheses" count = entries with `confidence: hypothesis` OR missing `confidence` field, AND `status: active`.
- "Rules" count = entries with `confidence: rule` AND `status: active`.
- "Most recent promotion" = max `date` across rules where the rule was promoted (track via the most recent entry in `confirmations[]`, since that is when the threshold was crossed).
- "Recent demotions" = count of entries with non-empty `contradicted_by[]` whose latest demotion date is within the last 30 days.
- "Top 5 hypotheses near promotion" = hypotheses with the highest `confirmation_count`, ties broken by recency. Show count, type, and summary.

---

### `compile` -- Compile execution feedback into patterns and run lifecycle passes

**Trigger:** `/insights compile` or `/insights compile since YYMMDD`

Performs three passes in order: skill-pattern compilation, hypothesis-to-rule promotion, rule-to-hypothesis demotion.

#### Pass 1: skill_pattern compilation (existing)

Reads execution feedback entries (`edge_case`, `correction`) across `_insights.yaml` files and compiles recurring patterns into `skill_pattern` entries.

1. **Scan all `_insights.yaml` files** in the vault for entries where `source.skill` is set (execution feedback)
2. **Filter by date** if `since YYMMDD` is specified
3. **Group by similarity** -- cluster entries by tags, summary keywords, and `source.skill` + `source.step`
4. **Apply compile threshold** -- only groups with `compile_threshold` (default: 3) or more entries become patterns. Read threshold from `workflows.knowledge_extraction.evolution.compile_threshold` in config.
5. **For each qualifying group, create a `skill_pattern` entry** in the most relevant `_insights.yaml`:
   ```yaml
   - id: [next_id]
     type: skill_pattern
     date: YYMMDD          # today
     summary: "Pattern description synthesized from N occurrences"
     detail: "Dates, contexts, and common resolution across occurrences"
     source:
       skill: insights/compile
       entries: [list of source entry IDs and their _insights.yaml locations]
     tags: [synthesized, from, source, entries]
     status: active
   ```
6. **Dedup** -- if a `skill_pattern` entry already covers the same cluster (by matching `source.entries`), update it instead of creating a duplicate
7. **Name-candidate roll-up (CR-095) -- a report, never a write.** While clustering, collect the
   entries in the proper-noun family (the `edge_case` entries `/transcript` and `/ops` log for an
   unresolved or near-miss name -- CR-016, CR-017). Identify them by tag, in either language:
   `proper-noun`, `egennamn`, `namnverifiering`, `namnupplösning`, `asr-variant`, `stavning`, `cr-016`
   -- the family is logged in the vault's working language, and an English-only match finds a fraction. Extract the flagged name from each
   `summary`/`detail`, and count occurrences **across folders**. Skip the flag marker itself (`Namn att verifiera`, `Name to verify`) -- it is quoted in the entries and is not a name. Drop any name that already resolves:
   a `people[].canonical` or alias in any `_ops.yaml` / `.claude/ops-config.yaml`, a `team[]` name or
   alias, or a contact's `display_name`/`aliases` in `_contacts/*/_meta.yaml`. Report every remaining
   name flagged **twice or more**, with a one-line guess at what it is (recurring person without a
   contact folder / ASR variant of a domain term / mishearing of a company name).

   **It never edits a config.** A roster entry is a claim about who someone is, and that claim needs
   a person: an automated identity claim would propagate a wrong spelling into every later summary --
   the failure CR-016 exists to prevent. The report is printed in the run's output only, because the
   names come from vault data; it is never written to a file in this repo.

   **Why here and not in `/transcript`:** one transcript run sees one occurrence, and one occurrence
   is not evidence of a recurring person. The recurrence is only visible in aggregate, which is where
   compile already operates. Before CR-095 the flags had no reader at all -- one measured vault held 98
   proper-noun flags over 40 folders and four months, against an empty `people[]` roster.

#### Pass 2: hypothesis → rule promotion (CR-013)

For each folder's `_insights.yaml`, find clusters of confirmed hypotheses and promote the canonical entry to a rule.

**The arithmetic is a script (CR-100).** Steps 1-3's deterministic gates -- candidate filter, type,
the tag gate, threshold, distinct dates, single-session -- are computed by
`skills/insights/promotion_candidates.py <folder>`, which prints the candidate groups as JSON with
a `group_key` and a `review_state` each. Use its output rather than re-deriving the gates; a review
surface uses the same script, so both see the same candidates. The script never judges: semantic
agreement, topic tags (unless `insight_topic_tags` is configured), splitting and the contradiction
check below stay judged. Its groups are connected components of the tag gate, so a group may hold
more than one claim -- that is what *split before promoting* is for.

1. **Filter to candidates:** entries where `confidence` is `hypothesis` (or absent) AND `status: active` AND `type` is one of `decision | preference | learning | pattern` (skip `opportunity`, `quote`, `metric`, `edge_case`, `correction`, `skill_pattern`).
   **`metric` is never promotable (CR-046):** the same measurement recurring three times is a time series, not a standing instruction. Trend questions belong in `/analytics`.
2. **Group by similarity within the folder (CR-094):**
   - Same `type`
   - **At least two shared tags**, or one shared tag when it is the primary tag (first in `tags[]`)
     of both entries
   - **Semantic agreement on the claim, judged rather than counted:** two summaries group when they
     assert the same thing about the same kind of subject, even with no shared vocabulary. Paraphrase
     is the norm, not the exception -- the corpus is model-written, and each entry is phrased freshly
     even when the observation recurs.
   - **Token overlap is not a gate.** It may break a tie when an entry could join more than one group.
   - **A shared tag that names a topic is not evidence of a shared claim (CR-098).** When the tags
     two entries share are subject areas (`seo`, `payments`, `architecture`, a supplier name) rather
     than properties of the claim, the tag overlap carries no weight -- decide on the summaries
     alone. A group that survives only on topic tags is not a group.
   - **Split before promoting (CR-098).** When a candidate group turns out to hold two or more
     distinct claims, split it and apply the threshold to each part. A six-entry group that is really
     four-plus-two yields one promotion, not one rule with two claims in it.
   - Conservative bias is kept through the tag requirement and the threshold: prefer false negatives
     over false promotions. A promoted rule becomes a standing instruction in every later run, so a
     wrong promotion costs more than a missed one.

   **Why not a word-overlap threshold:** before CR-094 this step required ≥60 % token overlap. On one
   mature vault (2 585 entries, 1 892 promotable hypotheses, 1 404 candidate pairs) the highest
   overlap anywhere was 0.50, so the gate could not fire and the rule layer held 5 entries -- the
   rules-walk in `/ops` and `/transcript` loaded an almost empty preamble every run. **Baseline for
   comparison: 5 rules before the change.**
3. **Apply threshold:** groups with size ≥ `compile_threshold` (default 3, from `workflows.knowledge_extraction.evolution.compile_threshold`) qualify. A group whose entries all share **one date** is skipped as `single session`: it is one observation, however many entries it produced (CR-098).
3a. **Honour recorded reviews (CR-100).** Read each group's `review_state` from the script:
    - `approved` (the key still matches): promote this group as judged -- **but step 3b still
      runs.** An approval does not bypass the contradiction check; a reversal is exactly what a
      reviewer can miss. A group that fails 3b is reported `needs review: contradiction (approved
      <date>)` and not promoted.
    - `stale` (a judged entry changed, a member was added or superseded since): the old approval or
      rejection no longer describes the group. Do not act on it; report `review stale` and treat the
      group as unreviewed.
    - `rejected` (the key still matches): skip; report it with the recorded `reason`. It is not
      re-offered until the group changes, which makes the review stale.
    - A valid subset review under `subset_reviews` (a split, `split_from` equal to the current key):
      that subset is a group of its own, with its own canonical; the remainder is re-evaluated
      against the threshold.
    - `unreviewed`: with `workflows.knowledge_extraction.evolution.promotion_review: optional` (the
      default), judge the group as below. With `required`, do not promote it; report it as
      `awaiting review`.
3b. **Contradiction check (CR-098).** Before promoting, read the group for entries that reverse or
    negate each other -- a decision and its later reversal, a pattern and an entry saying it no
    longer holds. Such a group must **not** be promoted on the earliest entry, because the earliest
    is the superseded one. Either:
    - mark the superseded entry `status: superseded`, `superseded_by: <later id>` and re-apply the
      threshold to what remains, or
    - skip the group and report it as `needs review: contradiction`.

    A reversal shares more vocabulary with what it reverses than two paraphrases of one principle
    share with each other, so no similarity measure catches this -- not token overlap, not tags. It
    has to be read.
4. **For each qualifying group:**
   - Pick the **earliest** entry by `date` as canonical.
   - Set `canonical.confidence: rule`.
   - Set `canonical.confirmation_count` to the number of **distinct dates** in the group, not the
     number of entries (CR-098). Entries written from one session are one observation however many
     were extracted. Every entry still goes into `confirmations[]` -- the provenance is kept, only the
     count is honest.
   - Append each non-canonical entry's `source` to `canonical.confirmations[]`.
   - Mark each non-canonical entry `status: superseded`, `superseded_by: <canonical-id>`.
5. **Dedup:** if the canonical entry already has `confidence: rule`, only append new (not-already-listed) confirmations and update `confirmation_count` (distinct dates).
6. **Report every skipped group with its reason (CR-098)** -- `topic-only tags`, `contradiction`,
   `split: below threshold`, `single session`, and the review states from 3a (`rejected: <reason>`,
   `review stale`, `awaiting review`, `needs review: contradiction (approved <date>)`) -- not just
   the promotions. Keep `promotion_review` on a promoted canonical: it records who approved it. A promotion pass that
   reports only its successes cannot be audited. Measured when CR-098 was written: of the ten largest
   candidate groups on one vault, **one** was promotable as-is, so single-digit promotions per run is
   the expected order of magnitude, not a sign the pass is broken.

#### Pass 3: rule → hypothesis demotion (CR-013)

Skipped if `workflows.knowledge_extraction.evolution.demote_on_contradiction` is `false`. Default: `true`.

1. **Find all rules:** entries with `confidence: rule` AND `status: active`, across the folder chain (current folder and parents per CR-011 walking resolution).
2. **For each rule, scan for targeting corrections:** `correction` entries newer than the rule's most recent confirmation, that:
   - Share the rule's primary tag (first tag in `tags[]`).
   - Contain an opposing keyword in `summary` (heuristic: rule says "always X" and correction says "do not X" or "X was wrong"; rule says "prefers Y" and correction says "does not prefer Y").
3. **For each rule with at least one targeting correction:**
   - Set `rule.confidence: hypothesis`.
   - Append each correction's `source` to `rule.contradicted_by[]`.
   - Reduce `rule.confirmation_count` by the number of contradictions, floor at 1.
4. **Do not delete confirmations.** History is preserved in `confirmations[]` for audit.

**Output:**

```
Compiling execution feedback and running lifecycle passes...

Pass 1 (skill_pattern compilation):
  Scanned 14 _insights.yaml files, found 23 execution feedback entries.
  Compiled 3 patterns.
  Skipped 8 entries below compile threshold (3).

  Roster candidates (flagged 2+ times, in no people[] roster, team[] or contact):   (CR-095)
    "Ravee"     4 flags   3 folders   → recurring person, no contact folder
    "Kubernetis" 3 flags   2 folders   → likely ASR variant of a domain term
    "Acmee"     2 flags   1 folder    → likely mishearing of a company name
  Add the ones that are people to people[] in the org config; the flags stop for them.

Pass 2 (hypothesis → rule promotion):
  Scanned 142 active hypotheses across 14 folders.
  Promoted 4 rules:
    - meetings/management/_insights.yaml#17 [preference] "Bob prefers concise weekly summaries" (3 confirmations)
    - _contacts/bob-smith/_insights.yaml#8 [pattern] "Bob raises customer-success topics last in 1-on-1s" (4 confirmations)
    - meetings/board/_insights.yaml#5 [decision] "Board updates use English for India team" (3 confirmations)
    - meetings/marketing/ppc/_insights.yaml#12 [learning] "PMax campaigns underperform below $500/day budget" (3 confirmations)
  Marked 8 superseded entries (canonical's mergees).
  Skipped 6 groups:                                                        (CR-098)
    - meetings/marketing/_insights.yaml [learning] "seo" (6)        topic-only tags
    - projects/app/_insights.yaml [decision] "architecture" (4)     contradiction -- needs review
    - projects/app/_insights.yaml [pattern] "estimation" (5)        single session (1 date)
    - ...

Pass 3 (rule → hypothesis demotion):
  Scanned 18 active rules.
  Demoted 1 rule:
    - meetings/management/_insights.yaml#11 [preference] "Always include Risk Assessment section"
      contradicted by 260507-summary.md (Bob asked to skip risk section for routine weeks)
```

#### Compile freshness stamp (CR-020)

After all three passes, write `last_compiled: YYMMDD` as a top-level field in every `_insights.yaml` the run scanned (additive; v1/v2 readers ignore it). This makes compile-staleness detectable: `/insights status` and `/ops check` flag when the newest entry in a file is >30 days newer than its `last_compiled` (or when the field is absent entirely -- i.e. compile has never run). Insights are write-heavy by design; the stamp is what keeps the synthesis half of the loop honest.

---

### `migrate` -- One-shot schema migration for drifted _insights.yaml files (CR-020; was `normalize`)

**Renamed in v1.79.0 (CR-089); `/insights normalize` was removed in v1.81.0.** An old name is not recognised: say that it is now `/insights migrate`, and do not run it.

**Trigger:** `/insights migrate [path] [--dry-run]` (no path = whole vault; `--dry-run` is the DEFAULT — writing requires explicit `--apply`)

Brings legacy and drifted `_insights.yaml` files up to the current schema. Complements `/ops normalize` (which fixes Swedish characters); this fixes structure and vocabulary.

**What it fixes, per entry:**

| Drift | Fix |
|-------|-----|
| ISO date `"2026-03-09"` | → `260309` (YYMMDD) |
| String ids (`bp-017`, `board-003`) | → sequential integers preserving order; record old id as `legacy_id` |
| Missing `type` | → infer from content; if ambiguous, set the most likely type and append `type-inferred` to tags for review |
| Non-canonical types (`decision-pattern`, `principle`, `outcome`, `design`, ...) | → map to nearest canonical (`pattern`, `learning`, `learning`, `decision`) |
| Non-canonical confidence (`high`, `confirmed`, `supported`) | → `hypothesis` + `confirmation_count: 2` (the writer evidently meant "confirmed once more") |
| Non-canonical confidence (`medium`, `low`) | → `hypothesis` (count unchanged) |
| Legacy fields (`added:` → `date`, string `source:` → `source.file`) | → rename/restructure |
| `superseded_by` set but `status: active` | → `status: superseded` |
| Entries below the top-level `next_id` key -- **orphans** (CR-096) | → re-indent into `insights`, renumber from `max(id)+1` to avoid collisions, splice before `next_id`, then bump it. **Report the recovery with its count** -- an orphan means some writer appended at end-of-file. Detect by reading the raw text, not the parsed file: an orphan either breaks the parse or is invisible to it |
| Missing top-level `version`/`next_id`/`last_updated` | → add (`version: 2`, `next_id: max(id)+1`, `last_updated` from newest entry). **Recover orphans first** -- `max(id)` over the parsed list ignores them, and repairing `next_id` around invisible entries collides with their ids |
| Tags > 5 | → truncate from the tail |

**Process:**
1. Scan target for `_insights.yaml` files (skip `.archive/`, `.transcripts/`, `.handoff/`, `clones/`)
2. Report every proposed change grouped by file (dry-run output = a unified change list with counts per drift class)
3. On `--apply`: write files atomically, add a one-line CHANGELOG entry in each affected folder (`- **YYMMDD: Normalize** _insights.yaml -- N schema fixes (CR-020)`)
4. Never touch entry content (`summary`/`rationale` text) -- structure and vocabulary only; content remediation stays with `/ops normalize`

---

### `synthesize` -- Render insight clusters into the knowledge wiki (CR-027)

**Trigger:** `/insights synthesize [topic|all]` (no argument = all clusters above threshold)

Turns the accumulated `_insights.yaml` corpus into a **curated wiki**: readable, crosslinked topic articles plus a master index. This is the synthesis layer the atomic entries feed — compile promotes *rules* (machine-facing context), synthesize produces *articles* (human- and session-facing knowledge). Clustering here is **semantic and vault-wide** (cross-folder, by topic), deliberately unlike compile Pass 2 (per-folder, gated by type and shared tags): prose summaries rarely repeat verbatim, so neither token overlap nor a single folder can build this layer. Pass 2 judges agreement semantically too since CR-094, but only inside one folder.

**Config** (`workflows.knowledge_synthesis` in base.yaml or org/vault config):

| Key | Default | Meaning |
|-----|---------|---------|
| `enabled` | `true` | Master switch |
| `article_threshold` | `5` | Minimum insights in a topic cluster to warrant an article |
| `wiki_path` | `.knowledge/wiki` | Article location, vault-relative |

**Process:**

1. **Load the corpus:** every `_insights.yaml` (skip `.archive/`, `.transcripts/`, `.handoff/`, `clones/`), entries with `status: active`. Types `quote` and `opportunity` are *supporting* material (quotes illustrate; opportunities go in an article's open-questions section) — they never drive cluster selection alone.
2. **Cluster by topic, vault-wide:** group semantically related insights across folders (shared tags are the seed; semantic judgment merges synonymous tags and splits overloaded ones). A cluster ≥ `article_threshold` (or an explicitly requested `<topic>`) gets an article.
3. **Write/update the article** at `<wiki_path>/<topic-slug>.md`:
   - **Topic-named, not dated** — articles are living documents (like rolling plans), never `YYMMDD-` prefixed.
   - **Frontmatter:** `sources:` (list of `file#id` for every insight used), `updated: YYMMDD`, `related: [slugs]`.
   - **Body:** synthesized prose — what has been learned, how thinking evolved, points of tension between insights, and an open-questions section. Verbatim `quote` entries may be quoted with attribution. Obsidian `[[wikilinks]]` to related articles. Language per the vault language policy; names allowed (CR-020 reusability note applies — this wiki is private).
   - **Vocabulary is canonicalized at synthesis:** known ASR-legacy terms in source insights are written correctly in articles (source entries stay untouched — the wiki is the cleaned layer).
   - **Verticals boundary:** if the topic matches a registered `workflows.verticals` doc, the article LINKS to the vertical for org-operational state instead of duplicating it.
4. **Idempotent refresh:** re-running updates an article only from insights not yet in its `sources:`, appending/reworking sections as needed and bumping `updated:`. An article containing `<!-- human-edited -->` is NEVER auto-updated — report it and ask (flag once, respect the answer).
5. **Maintain `<vault>/.knowledge/INDEX.md`:** one line per article — `[[slug]] — one-sentence hook (updated YYMMDD)` — grouped by theme; plus an **Osorterat** section listing loose notes at the `.knowledge/` root (legacy notes are never modified or moved by this skill).
6. **Report:** articles created/updated/skipped(human-edited), clusters below threshold (with counts, so growth toward threshold is visible).

**How the wiki is USED (the no-RAG contract):** a session answering a knowledge question reads `INDEX.md` first, then opens only the relevant article(s). No vault-wide scanning, no retrieval infrastructure. The INDEX is maintained by this subcommand only — never hand-edited by other skills.

---

### `propose` -- Generate SKILL.md improvement proposals

**Trigger:** `/insights propose`

Reads compiled `skill_pattern` entries and generates concrete SKILL.md improvement proposals.

**Steps:**

1. **Scan all `_insights.yaml` files** for `skill_pattern` entries
2. **Apply propose threshold** -- only patterns with `propose_threshold` (default: 5) or more underlying occurrences generate proposals. Read threshold from `workflows.knowledge_extraction.evolution.propose_threshold` in config.
3. **For each qualifying pattern:**
   a. Read the target SKILL.md file (determined by `source.skill`)
   b. Identify the relevant step or section
   c. Generate a concrete proposed change as a markdown file

4. **Save proposals** to `workflows.knowledge_extraction.evolution.proposals_path` — **default `docs/proposals/generated/` inside this repo**, gitignored until reviewed (CR-083).

   *This reverses CR-028's "never inside the skill repo".* The reason CR-028 gave was privacy: proposals derive from vault data. But **a proposal is an instruction** — it says how a skill should work — and instructions belong where the skill lives, or they are reviewed nowhere the repo can see. Written into the vault they never entered the index and no session reading the repo could find them.

   **The privacy requirement is unchanged and is met differently:** a generated proposal is written **name-free**, the same rule every CR since CR-047 follows, and the pre-push guard scans it on the way out. **Evidence stays in the vault** — a proposal may say *observed in one vault*; it never needs the names to be right.

   A vault may still override the path, but nothing in this repo assumes it does. Format:
   ```markdown
   # Proposal: [Short title]

   **Based on:** [N] occurrences of [pattern summary]
   **Target:** skills/[skill]/SKILL.md
   **Pattern entry:** [reference to skill_pattern in _insights.yaml]

   ## Proposed change

   **In section:** [Step or section name]
   **Action:** [Add/Modify/Remove]

   > [Exact text to add or replace, written in the same style as the target SKILL.md]

   ## Rationale

   [Why this change addresses the pattern, with references to specific occurrences]
   ```

5. **Check auto_apply setting** from `workflows.knowledge_extraction.evolution.auto_apply`:
   - If `false` (default): save proposal file, report to user
   - If `true`: apply the change to SKILL.md directly, log in CHANGELOG with `[AUTO-EVOLUTION]` tag, save proposal as record

**Output (auto_apply: false):**

```
Generated 2 proposals:

1. <proposals_path>/260404-transcript-name-disambiguation.md
   Target: skills/transcript/SKILL.md (Step 1)
   Based on: 7 edge_case entries across 4 contacts

2. <proposals_path>/260410-ops-short-call-threshold.md
   Target: skills/transcript/SKILL.md (Step 4)
   Based on: 5 skill_pattern entries

Review proposals and run /insights propose apply to accept.
```

**Output (auto_apply: true):**

```
Auto-applied 2 proposals:

1. skills/transcript/SKILL.md -- added name disambiguation pre-check (Step 1)
   [AUTO-EVOLUTION] Based on 7 edge_case entries

2. skills/transcript/SKILL.md -- added short-call skip for task import (Step 4)
   [AUTO-EVOLUTION] Based on 5 skill_pattern entries

Changes logged in CHANGELOG.md. Review with: git diff HEAD~1
```

---

### `propose apply` -- Apply a proposal

**Trigger:** `/insights propose apply [proposal-file]` or `/insights propose apply all`

**Steps:**

1. **If specific file:** Read the proposal, apply the change to the target SKILL.md
2. **If `all`:** Apply all proposals in the configured `proposals_path` that haven't been applied yet
3. **For each applied proposal:**
   a. Edit the target SKILL.md with the proposed change
   b. Add a CHANGELOG entry with `[EVOLUTION]` tag
   c. Move the proposal file to `<proposals_path>/.applied/` with a date prefix
   d. Mark the source `skill_pattern` entry as `status: archived` in `_insights.yaml`

**Output:**

```
Applied: <proposals_path>/260404-transcript-name-disambiguation.md
  -> skills/transcript/SKILL.md updated (Step 1: added pre-check)
  -> CHANGELOG.md updated
  -> Proposal archived to <proposals_path>/.applied/

1 proposal applied. Review changes with: git diff
```

---

### `help` -- Usage guide

**Trigger:** `/insights help`

**Output:**

```
/insights -- Knowledge Extraction & Skill Evolution
=====================================================

Backfill _insights.yaml, compile execution feedback, and evolve skills.

Usage:
  /insights reprocess =bob-smith          Extract from one folder
  /insights reprocess all                Extract from all folders with CHANGELOG.md
  /insights reprocess since 260201       Extract from files dated after 260201
  /insights scan-claude-md               Extract from CLAUDE.md files
  /insights compile                      Compile execution feedback into patterns
  /insights compile since 260301         Compile only recent feedback
  /insights migrate [path] [--apply]   Migrate drifted files to current schema (dry-run default)
  /insights synthesize [topic|all]       Render insight clusters into wiki articles + INDEX (CR-027)
  /insights propose                      Generate SKILL.md improvement proposals
  /insights propose apply [file|all]     Apply a proposal to SKILL.md
  /insights status                       Show insights + evolution statistics
  /insights help                         This guide

Knowledge extraction (content):
  /transcript Step 3.5    Extracts insights from NEW transcripts (forward)
  /ops Step 5.5           Extracts insights from NEW meeting docs (forward)
  /insights reprocess     Extracts insights from EXISTING transcripts (backfill)
  /insights scan-claude-md  Extracts from CLAUDE.md files

Skill evolution (execution feedback):
  /ops Step 9             Captures edge cases and corrections (silent)
  /transcript Step 4.5    Captures edge cases and corrections (silent)
  /insights compile       Finds patterns in execution feedback
  /insights propose       Generates SKILL.md improvements from patterns

Data flow:
  /transcript or /ops ─┐
                       ├── _insights.yaml ──> visualisation app
  /insights ───────────┘       │
                               ├── execution feedback (edge_case, correction)
                               │        │
                               │   /insights compile -> skill_pattern entries
                               │        │
                               │   /insights propose -> SKILL.md improvements
                               │        │
                               └── skills get smarter over time

Config: workflows.knowledge_extraction.evolution in base.yaml
  auto_apply: false (default) -- proposals require manual review
  auto_apply: true -- proposals applied automatically
```

---

## Extraction Reference (Quick Copy)

This is a convenience copy. The authoritative definition is in `/transcript` Step 3.5 (`~/.claude/skills/transcript/SKILL.md`).

| Type | Captures | Written by |
|------|----------|------------|
| `decision` | Choice + rationale | `/transcript`, `/ops`, `/insights reprocess` |
| `preference` | Working style | `/transcript`, `/ops`, `/insights reprocess` |
| `learning` | What worked/didn't | `/transcript`, `/ops`, `/insights reprocess` |
| `opportunity` | Ideas not yet actioned | `/transcript`, `/ops`, `/insights reprocess` |
| `pattern` | Recurring themes | `/transcript`, `/ops`, `/insights reprocess` |
| `quote` | Memorable verbatim quote (exempt from promotion -- never becomes a rule) | `/transcript`, `/ops`, `/insights reprocess` |
| `metric` | A measurement where the number is the claim (exempt from promotion -- CR-046) | `/transcript`, `/ops`, `/insights reprocess` |
| `edge_case` | Skill hit ambiguous input | `/transcript` Step 4.5, `/ops` Step 9 |
| `correction` | User corrected skill output | `/transcript` Step 4.5, `/ops` Step 9 |
| `skill_pattern` | Compiled execution pattern | `/insights compile` |

**`metric` vs `learning` (CR-046):** remove the number from the summary. Nothing left -> `metric`. Claim survives -> `learning` citing evidence.

**Threshold:** Only non-obvious, durable, specific insights. If fewer than `min_insights` (default: 1) qualify, skip the file silently.

**Format:** See `/transcript` Step 3.5 for the complete `_insights.yaml` schema.

---

## Reusability and language rules for _insights.yaml

### Reusability (CR-020 -- replaces the former privacy rule)

Names are **allowed** in insight entries. The old rule ("never include personal or company names in `summary`, `rationale`, or `tags`") was retired in the 2026-04-07 audit and formally removed by CR-020 -- the vault is private, and `quote` entries need attribution by construction. What remains is a *reusability preference*: when an insight generalizes beyond the person who sparked it, prefer name-free phrasing in `summary` and `tags` so the entry reads well in cross-folder views -- `context` and `source.file` already carry the who/what. Names in `rationale` are always fine. See `/transcript` Step 3.5 for examples.

#### Contact Classification (CR-009)

Insights are extracted from all contacts regardless of their `classification` (family, personal, professional, confidential) — knowledge is valuable in all contexts. The `core-skills-visualisation` app is responsible for respecting classification when rendering.

### Vocabulary guard (CR-020)

Before writing any entry, apply the write-time vocabulary guard defined in `/transcript` Step 3.5: canonical `type` values only, `confidence` limited to `hypothesis | rule`, `YYMMDD` dates, integer ids with `next_id` maintained, max 5 tags. This applies to every subcommand in this skill that writes entries.

### Swedish Character Enforcement (CR-007: pre-write validator)

When processing Swedish-language content, ALL Swedish words MUST use correct å, ä, ö characters. Never substitute with a or o. This applies equally to `_insights.yaml` YAML fields as to markdown files -- YAML content is just as prone to missing characters.

**Pre-write validator (CR-007, mandatory):**

Before writing **any** entry to `_insights.yaml` where the `summary`, `rationale`, or `context` field is in Swedish (detected by presence of any å/ä/ö character anywhere in the file, OR by folder language per `language_inheritance` in base.yaml):

1. Load `~/.claude/skills/ops-config/swedish_substitutions.yaml`
2. Scan each Swedish text field token-by-token using word-boundary matching
3. For each match against the substitution map (excluding `category: skip`):
   - **Non-ambiguous** matches: refuse to write, report the violation, ask the user to confirm or fix
   - **Ambiguous** matches: warn but proceed (log to execution feedback as `edge_case`)
4. If no violations, proceed with the write

This is the structural validation that the MEMORY.md reminder cannot provide -- the model may write a tempting `"...byggs fran Meta..."` line, but the validator catches it before the file lands on disk.

**To remediate existing files** written before this validator was added, run `/ops normalize <path>`.

Common mistakes to avoid (full list in `swedish_substitutions.yaml`):
- "pagaende" -> "pågående", "foretag" -> "företag", "fran" -> "från"
- "fore" -> "före", "nasta" -> "nästa", "behovs" -> "behövs", "kravs" -> "krävs"
- "anvandning" -> "användning", "mote" -> "möte", "losning" -> "lösning"
- "manader" -> "månader", "forberedelse" -> "förberedelse", "karnteam" -> "kärnteam"
- "prissattning" -> "prissättning", "overgang" -> "övergång", "forandring" -> "förändring"

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.