narrative-topology

Extract semantic relationships from long narratives, architectures, or complex discussions using RDF-style triple notation. Generate adjacency matrices to reveal narrative structure, dependency graphs, and critical paths. Works standalone or paired with all-dialogue-to-markdown. Extracts signal from noise in dense multi-turn discussions.

Safety Notice

This listing is from the official public ClawHub registry. Review SKILL.md and referenced scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "narrative-topology" with this command: npx skills add turinfohlen/narrative-topology

Narrative Topology

Core Concept

Problem: Long discussions (100+ messages, architectural debates, narrative analysis) lose structure. Easy to miss critical dependencies, causality chains, or bottlenecks.

Solution: Embed RDF-style triples in markdown. Scan with Python. Extract semantic adjacency matrix. See structure.


Triple Notation

Format


<<{Subject, Predicate, Object}.

Examples

Simple relations:


<<{Claude, outputs, analysis}.
<<{analysis, informs, decision}.

Parallel subjects (cartesian product):


<<{[A, B, C], implements, feature}.

Expands to:


{A, implements, feature}
{B, implements, feature}
{C, implements, feature}

Parallel objects:


<<{payment_system, affects, [latency, cost, security]}.

Expands to:


{payment_system, affects, latency}
{payment_system, affects, cost}
{payment_system, affects, security}

Both parallel:


<<{[Claude, User], collaborated_on, [design, implementation]}.

Full cartesian product: 2×2 = 4 edges.

Syntax Rules

  • <<{ starts the triple
  • }. (period-dot) terminates
  • Commas separate subject, predicate, object
  • Whitespace trimmed automatically
  • [...] denotes list; bare tokens are singletons
  • Lines in markdown, easily greppable

Why This Format

  1. RDF-like — Semantic web standard, well-understood
  2. Grep-friendlygrep '<<{' file.md finds all triples
  3. Unambiguous — Clear start/end, no nesting
  4. Markdown-native — Doesn't break rendering
  5. Compact — One line per relation

Example: Hamlet

Input markdown (partial):

## Act I

<<{Claudius, murders, old_king}.
<<{Claudius, usurps, Denmark_throne}.
<<{Claudius, marries, Gertrude}.

## Act II

<<{ghost, reveals, Hamlet}.
<<{ghost, demands, revenge}.
<<{Hamlet, feigns, madness}.
<<{Hamlet, kills, Polonius}.

## Consequences

<<{Polonius_death, causes, Ophelia_madness}.
<<{Ophelia, drowns, river}.

## Climax

<<{[Hamlet, Laertes], duel, each_other}.
<<{[poison_sword, poison_wine], kills, [Hamlet, Laertes, Claudius, Gertrude]}.
<<{Fortinbras, takes_over, Denmark}.

Output: Adjacency matrix showing all 14 nodes and their causal/narrative dependencies.


Python Scanner

Place scanner.py in your markdown directory. Run:

python scanner.py

Code

#!/usr/bin/env python3
"""
Narrative Topology Scanner
Usage: python scanner.py
Outputs adjacency matrix compressed with x::n notation
"""

import os
import sys

def list_files_recursive(directory, extensions=('.txt', '.def', '.erl', '.ex', '.md')):
    """Yield all files with given extensions under directory."""
    for root, _, files in os.walk(directory):
        for f in files:
            if f.endswith(extensions):
                yield os.path.join(root, f)

def parse_list(s):
    """Parse a token that may be a singleton or a bracketed list."""
    s = s.strip()
    if s.startswith('[') and s.endswith(']'):
        inner = s[1:-1].strip()
        if not inner:
            return []
        return [item.strip() for item in inner.split(',')]
    return [s]

def smart_split(content):
    """
    Split content by commas while respecting nested brackets.
    Returns list of three parts: subject, predicate, object.
    """
    parts = []
    current = []
    depth = 0
    for ch in content:
        if ch == ',' and depth == 0:
            parts.append(''.join(current).strip())
            current = []
        else:
            if ch == '[':
                depth += 1
            elif ch == ']':
                depth -= 1
            current.append(ch)
    if current:
        parts.append(''.join(current).strip())
    return parts if len(parts) == 3 else None

def process_line(line):
    """Extract triples from a single line."""
    line = line.strip()
    if not line.startswith('<<{'):
        return []
    if not line.endswith('}.'):
        return []
    # Extract content between <<{ and }.
    content = line[3:-2]
    parts = smart_split(content)
    if not parts:
        return []
    s, p, o = parts  # p is ignored (predicate)
    subjects = parse_list(s)
    objects = parse_list(o)
    edges = []
    for subj in subjects:
        for obj in objects:
            edges.append((subj, obj))
    return edges

def process_file(filepath):
    """Process a single file, returning list of (source, target) edges."""
    edges = []
    try:
        with open(filepath, 'r', encoding='utf-8') as f:
            for line in f:
                edges.extend(process_line(line))
    except Exception:
        pass
    return edges

def compress_row(row):
    """Convert list of ints to x::n compressed string."""
    if not row:
        return ''
    compressed = []
    current_val = row[0]
    count = 1
    for val in row[1:]:
        if val == current_val:
            count += 1
        else:
            compressed.append(f"{current_val}::{count}")
            current_val = val
            count = 1
    compressed.append(f"{current_val}::{count}")
    return ','.join(compressed)

def main():
    cwd = os.getcwd()
    edges = []
    for filepath in list_files_recursive(cwd):
        edges.extend(process_file(filepath))

    # Gather unique nodes
    nodes = set()
    for s, o in edges:
        nodes.add(s)
        nodes.add(o)
    nodes = sorted(nodes)
    n = len(nodes)

    # Build index map
    idx = {node: i for i, node in enumerate(nodes)}

    # Build adjacency matrix (list of lists)
    matrix = [[0] * n for _ in range(n)]
    for s, o in edges:
        i, j = idx[s], idx[o]
        matrix[i][j] = 1

    # Output
    print("Nodes: " + ", ".join(nodes))
    print()
    print("Adjacency Matrix (compressed x::n):")
    print("# x::n = n copies of value x")
    for row in matrix:
        print(compress_row(row))

    print()
    print("Stats:")
    print(f"Nodes: {n}")
    print(f"Edges: {len(edges)}")
    if n > 0:
        density = len(edges) / (n * n)
        print(f"Density: {density:.4f}")

if __name__ == "__main__":
    main()

Output

The scanner outputs an adjacency matrix compressed with x::n notation (n copies of value x):

Nodes: Claudius, Denmark_throne, Fortinbras, Gertrude, Hamlet, Laertes, Ophelia, Polonius, Polonius_death, ...

Adjacency Matrix (compressed x::n):
# x::n = n copies of value x
0::14
0::3,1::1,0::10
0::4,1::2,0::8
1::14
...

Stats:
Nodes: 14
Edges: 12
Density: 0.0612

Format rule: x::n means n consecutive occurrences of value x. Example: 0::2,1::3,0::1 expands to [0,0,1,1,1,0].

For AI analysis: The matrix rows = sources (in node list order), columns = targets. 1 = edge exists.


Interpreting the Matrix

Rows = Sources, Columns = Targets

Matrix M where M[i,j] = 1 means: Node_i → Node_j

Key Analyses

· In-degree (column sum): How many things cause this node? (Count 1s in each column) · Out-degree (row sum): How many things does this node cause? (Count 1s in each row) · Strongly connected components: Cycles (feedback loops, mutual causality) · Topological sort: Order events by causal dependency · Critical path: Chain with maximum bottleneck nodes


Extending the Scanner

Add Weighted Edges

Replace binary (0/1) with strength values:

# Instead of setting to 1, count occurrences
weight_matrix = [[0] * n for _ in range(n)]
for s, o in edges:
    i, j = idx[s], idx[o]
    weight_matrix[i][j] += 1

Output matrix has counts, not just binary.

Generate Mermaid Graph

Add to scanner:

print("graph LR")
for s, o in edges:
    print(f"  {s} --> {o}")

Pipe output to a .mmd file, render in claude.ai.

Generate GraphViz DOT

print("digraph {")
for s, o in edges:
    print(f'  "{s}" -> "{o}";')
print("}")

Render with dot, convert to PNG/SVG.


Use Cases

  1. Narrative Analysis Extract plot dependency from novel/screenplay. · Identify critical turning points (high in-degree) · Find orphaned subplots (isolated nodes) · Spot circular dependencies (tragedy, irony)
  2. Architectural Discussions Embed triples in design doc markdown:
    <<{microservice_A, calls, microservice_B}.
    <<{microservice_B, depends_on, database}.
    <<{database, shared_by, [service_C, service_D]}.
    
    Scan → see service coupling graph → identify decoupling opportunities.
  3. Project Workflows Task dependencies:
    <<{design_doc, blocks, implementation}.
    <<{implementation, requires, code_review}.
    <<{code_review, unblocks, deployment}.
    
    Scan → topological sort → critical path analysis.
  4. Technical Debt Mapping
    <<{legacy_code, causes, technical_debt}.
    <<{technical_debt, blocks, new_feature}.
    <<{new_feature, required_by, [OKR_1, OKR_2]}.
    
    Prioritize refactor based on downstream impact.

Integration with all-dialogue-to-markdown

Optional pairing:

  1. Claude writes analysis in markdown (using all-dialogue skill)
  2. You embed triples in the markdown as you read
  3. Run scanner on the output file → see structure
  4. Use matrix to: · Ask follow-up questions · Spot gaps · Verify causality chains

Example workflow:

User: "Analyze scheduler design. Save as scheduler-analysis.md"
Claude: [saves full analysis to scheduler-analysis.md]

User: [reads md, embeds triples]
<<{async_dispatch, reduces, latency}.
<<{latency, affects, throughput}.

User: "Run scanner on scheduler-analysis.md"
Scanner: [adjacency matrix]

User: [examines matrix] "What about error propagation?"

No requirement to use all-dialogue — works standalone on any markdown.


Workflow

Standalone Use

  1. Write markdown with embedded triples
  2. Place scanner.py in same directory
  3. Run: python scanner.py
  4. Get compressed adjacency matrix
  5. Use for analysis or feed back to AI

With all-dialogue Pairing

  1. Ask Claude to save analysis to markdown (all-dialogue skill)
  2. Read markdown, mark critical relations with triples
  3. Run scanner
  4. Iterate: use matrix to ask more precise questions

Philosophy

· Why separate from all-dialogue? All-dialogue manages token flow (thinking + response → file). Narrative-topology manages semantic extraction (relations → matrix). Independent concerns. Both can upgrade separately. Narrative-topology applies to any markdown, not just Claude output. · Why triples instead of prose summaries? Prose is lossy. Easy to miss connections. Triples are canonical, computable, greppable. Matrix enables quantitative analysis (paths, cycles, centrality). Scales: 50 triples → clear structure. 500 triples → still manageable. · Why Python? Ubiquitous, no extra dependencies. Single file, runs anywhere. Clear, readable, and easily extensible.


Summary

Narrative Topology extracts relational structure from dense text.

· Input: Markdown with <<{S, P, O}. triples · Processing: Python scanner parses + expands cartesian products · Output: Adjacency matrix compressed with x::n notation (low token cost, AI‑friendly) · Power: See causal chains, bottlenecks, cycles in long discussions · Scope: Works standalone. Pairs naturally with all-dialogue.

Use it to maintain signal in long, complex narratives.

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

General

TOKEN SOP

自动保存并本地调用已执行任务,避免重复消耗Token,实现离线秒级响应,提升效率与节省费用。

Registry SourceRecently Updated
General

Facebook-poster

Generuoja kasdienius įtraukiamus Facebook įrašus lietuvių kalba, kad sujungtų Qvicker.lt vartotojus su vietiniais paslaugų meistrais.

Registry SourceRecently Updated
General

TOKEN SOP

自动缓存并复用本地成功工作流,优先本地执行节省Token,支持断网使用和云端备份共享。

Registry SourceRecently Updated
General

generate-personal-brand-ad-creative-brief

Plan campaign visuals and hooks for personal brand promotions. Use when working on paid campaign planning for thought leaders, coaches, personal brand...

Registry SourceRecently Updated