Data Flows

Documentation > Architecture > Data Flows

Data Flow Diagrams

This document details how data moves through gsd-skill-creator for the three core pipelines: Skill Creation, Session Activation, and Pattern Detection to Agent Composition. Each flow includes the actual function calls and module paths from the codebase.


Flow 1: Skill Creation Pipeline

User runs `npx skill-creator create`
  → CLI parses command, determines scope (user/project)
  → Workflow collects input (name, description, body)
  → SkillValidator.validate(metadata)
  → BudgetValidator.checkSingleSkill(content.length)
  → GenerationSafety.scanForDangerousCommands(body)
  → PathSafety.validateSafeName(name)
  → PathSafety.assertSafePath(resolvedPath, baseDir)
  → SkillStore.create(name, metadata, body)
  → SkillIndex.rebuild()
  → .claude/skills/{name}/SKILL.md written to disk

Validation Gates

Three critical checkpoints before any skill reaches disk:

// Gate 1: Name validation (src/validation/skill-validation.ts)
const nameValidation = validateSkillNameStrict(skillName);
if (!nameValidation.valid) {
  const suggestion = nameValidation.suggestion;
  throw new Error(`Invalid skill name "${skillName}": ${nameValidation.errors.join('; ')}`);
}

// Gate 2: Reserved name check (src/validation/reserved-names.ts)
const reservedCheck = await validateReservedName(skillName);
if (!reservedCheck.valid) {
  throw new Error(reservedCheck.error);
}

// Gate 3: Budget check (src/validation/budget-validation.ts)
const budgetValidator = BudgetValidator.load();
const budgetCheck = budgetValidator.checkSingleSkill(content.length);
if (budgetCheck.severity === 'error') {
  console.warn(`Skill exceeds character budget (${budgetCheck.charCount} / ${budgetCheck.budget} chars)`);
}

Security Pipeline

// src/validation/path-safety.ts - Defense-in-depth traversal prevention
export function validateSafeName(name: string): SafeNameResult {
  // Checks: empty, null byte, ".." traversal, path separators, reserved names
}
export function assertSafePath(resolvedPath: string, baseDir: string): void {
  // Verifies resolved path stays within base directory
}

// src/validation/generation-safety.ts - Dangerous command detection
export const DANGEROUS_COMMANDS: DangerousCommandPattern[] = [
  { name: 'recursive-delete', pattern: /rm\s+(?:-[a-zA-Z]*)?r.../, description: '...' },
  { name: 'recursive-delete-rf', pattern: /rm\s+-rf\s+[\/~*].../, description: '...' },
  // ... additional patterns for chmod 777, curl|bash, dd if=/dev/zero, etc.
];
export function scanForDangerousCommands(content: string): DangerousFinding[];
export function sanitizeGeneratedContent(body: string): { sanitized: string; findings: DangerousFinding[] };

Progressive Disclosure Path

For skills exceeding 2000 words, the creation flow takes an alternate path through SkillStore.createWithDisclosure():

Large skill body (>2000 words)
  → ContentDecomposer.decompose(name, metadata, body)
  → Splits into compact SKILL.md + references/*.md + scripts/*.sh
  → ReferenceLinker.validateSkillReferences(skillDir)
  → Checks for circular references between files
  → Writes all files to .claude/skills/{name}/

Flow 2: Session Activation Pipeline

Session starts
  → SessionObserver.onSessionStart(data)
     Caches: { sessionId, transcriptPath, cwd, source, model, startTime }
  → SkillPipeline.process(context)
     Stage 1: ScoreStage    → match intent via TF-IDF or embeddings, score relevance
     Stage 2: ResolveStage  → resolve inheritance chains via SkillResolver
     Stage 3: ModelFilterStage → filter by quality/balanced/budget profile
     Stage 4: CacheOrderStage  → reorder to maximize cache hits
     Stage 5: BudgetStage   → enforce 2-5% context budget with tiered priority
     Stage 6: LoadStage     → inject selected skills into context
  → Skills active for session

The PipelineContext Object

All data flows through a single context object passed between stages:

// src/application/skill-pipeline.ts
export interface PipelineContext {
  // Inputs (read-only to stages)
  readonly intent?: string;
  readonly file?: string;
  readonly context?: string;

  // Intermediate results (stages read/write)
  matches: SkillIndexEntry[];         // ScoreStage writes
  scoredSkills: ScoredSkill[];        // ScoreStage writes
  resolvedSkills: ScoredSkill[];      // ResolveStage writes
  conflicts: ConflictResult;          // ResolveStage writes

  // Budget results
  budgetSkipped: SkippedSkill[];      // BudgetStage writes
  budgetWarnings: BudgetWarning[];    // BudgetStage writes
  contentCache: Map<string, string>;  // BudgetStage writes, LoadStage reads

  // Model profile for model-aware filtering
  readonly modelProfile?: string;     // 'quality' | 'balanced' | 'budget'

  // Final outputs
  loaded: string[];                   // LoadStage writes
  skipped: string[];                  // LoadStage writes
  earlyExit: boolean;                 // Any stage can set
}

ScoreStage Detail

// src/application/stages/score-stage.ts
export class ScoreStage implements PipelineStage {
  readonly name = 'score';

  constructor(
    private skillIndex: SkillIndex,
    private scorer: RelevanceScorer,
    private router?: AdaptiveRouter,        // Optional: TF-IDF vs embedding routing
    private embeddingService?: EmbeddingService,
  ) {}

  async process(context: PipelineContext): Promise<PipelineContext> {
    const matches = await this.skillIndex.findByTrigger(
      context.intent, context.file, context.context
    );
    context.matches = matches;
    if (matches.length === 0) {
      context.earlyExit = true;
      return context;
    }
    // Score each match by relevance...
  }
}

BudgetStage Detail

// src/application/stages/budget-stage.ts
export class BudgetStage implements PipelineStage {
  readonly name = 'budget';

  constructor(
    private tokenCounter: TokenCounter,
    private profile: BudgetProfile,
    private skillStore: SkillStore,
    private contextWindowSize: number = 200_000
  ) {}

  async process(context: PipelineContext): Promise<PipelineContext> {
    const standardBudget = this.tokenCounter.calculateBudget(
      this.contextWindowSize, this.profile.budgetPercent   // 2-5%
    );
    const hardCeiling = this.tokenCounter.calculateBudget(
      this.contextWindowSize, this.profile.hardCeilingPercent
    );
    // Partition into critical/standard/optional tiers
    // Critical loads even past standard budget, up to hard ceiling
    // Optional gets skipped first when budget is tight
  }
}

Pipeline Extensibility

// The pipeline supports inserting stages without modifying existing code
const pipeline = new SkillPipeline();
pipeline.addStage(new ScoreStage(index, scorer));
pipeline.addStage(new ResolveStage(resolver));
pipeline.addStage(new ModelFilterStage(profiles));
pipeline.addStage(new CacheOrderStage());
pipeline.addStage(new BudgetStage(counter, profile, store));
pipeline.addStage(new LoadStage(store));

// Insert a custom stage before budget enforcement
pipeline.insertBefore('budget', new MyCustomStage());

// Insert after scoring
pipeline.insertAfter('score', new MyPostScoreStage());

Flow 3: Pattern Detection to Agent Composition

Session ends
  → SessionObserver.onSessionEnd(data)
     → TranscriptParser.parse(transcriptPath)    // Parse Claude Code transcript
     → PatternSummarizer.summarize(entries)       // Extract behavioral patterns
     → ObservationRateLimiter.checkLimit(id)      // Rate limit check
     → PromotionEvaluator.evaluate(summary)       // Ephemeral or persistent?
     → PatternStore.append('sessions', summary)   // Append to JSONL

Pattern Detection (periodic)
  → PatternAnalyzer.analyze(sessionsPath)
     → Stream JSONL, build frequency maps
     → Filter by threshold (3+ occurrences)
     → Calculate recency-weighted confidence
     → Return SkillCandidate[] sorted by confidence

User Approves Candidate
  → SkillGenerator.generateScaffold(candidate)
     → scanForDangerousCommands(body)            // SEC-05
     → sanitizeGeneratedContent(body)             // Block dangerous content
     → inferAllowedTools(candidate)               // SEC-07
     → detectArguments(body)                      // SPEC-02
     → shouldForkContext(description, body)        // SPEC-05
     → ContentDecomposer.decompose(name, meta, body) // DISC-01
  → SkillStore.create(name, metadata, body)

Agent Composition (after N sessions)
  → CoActivationTracker.analyze(sessions)
     → Find skill pairs with 3+ co-activations
     → Score by frequency and recency
  → ClusterDetector.detectClusters(coActivations)
     → Group related skills into clusters
  → AgentGenerator.generateContent(cluster)
     → Load skill descriptions
     → validateAgentFrontmatter()
     → Format agent markdown with skills list
  → TeamValidator.validateTeamFull(config)        // If team topology
  → .claude/agents/{name}.md written to disk

PatternAnalyzer Detail

// src/detection/pattern-analyzer.ts
export class PatternAnalyzer {
  private config: DetectionConfig;

  async analyze(sessionsPath: string): Promise<SkillCandidate[]> {
    const freq = await this.countPatterns(sessionsPath); // Stream JSONL
    return this.extractCandidates(freq);
  }

  // Frequency map tracks commands, files, tools, co-occurrences, timestamps
  private initFrequencyMap(): FrequencyMap {
    return {
      commands: new Map(),
      files: new Map(),
      tools: new Map(),
      coOccurrences: new Map(),
      sessionTimestamps: new Map(),
      sessionIds: new Map(),
    };
  }

  // Confidence = base (occurrences/10, max 0.7) + recency boost (max 0.3)
  // Total capped at 1.0
}

CoActivationTracker Detail

// src/agents/co-activation-tracker.ts
export class CoActivationTracker {
  analyze(sessions: SessionObservation[]): SkillCoActivation[] {
    // Filter to recent sessions (within recencyDays)
    // Generate all skill pairs per session (combinations, not permutations)
    // Alphabetically sort pairs for consistent keys
    // Count co-activations, track sessions and timestamps
    // Return pairs exceeding minCoActivations threshold
  }

  getCoActivationScore(skillA: string, skillB: string, sessions: SessionObservation[]): number {
    // Score = frequencyScore * 0.7 + recencyBoost * 0.3
    // frequencyScore = coActivationCount / totalRecentSessions
    // recencyBoost = 1 - (daysSinceLastSeen / recencyDays)
  }
}

Observation Tier System

Session observation arrives
  → PromotionEvaluator.evaluate(summary)
     → Sufficient signal? (tool diversity, file count, command count)
     YES → tier = 'persistent' → PatternStore.append()
     NO  → tier = 'ephemeral'  → EphemeralStore.append()

After each session end:
  → ObservationSquasher.squash(ephemeralEntries)
     → Aggregate all ephemeral entries into one
     → Re-evaluate aggregate for promotion
     → Strong enough? → Promote to persistent
  → EphemeralStore.clear()
  → RetentionManager.prune(sessionsFile)

Shared Components Across Flows

Component Flow 1: Create Flow 2: Activate Flow 3: Detect/Compose
SkillStore Write skills Read skills (LoadStage) Read metadata, write generated skills
SkillIndex Rebuild after create Trigger matching (ScoreStage) Not used directly
EmbeddingService Not used Semantic scoring (ScoreStage) Not used
BudgetValidator Pre-write budget check Runtime budget enforcement (BudgetStage) Generated skill budget check
PathSafety Name/path validation Not used Agent file path validation
SkillResolver Not used Inheritance resolution (ResolveStage) Not used
PatternStore Not used Not used Append observations, read for analysis