Development Setup

Documentation > Developer Guide > Development Setup


Running Tests

# Run all tests
npm test

# Run specific test file
npm test src/agents/cluster-detector.test.ts

# Run tests in watch mode
npm run test:watch

# Run tests with coverage
npm run test:coverage

Building

# Compile TypeScript
npm run build

# Type check without emit
npx tsc --noEmit

# Clean and rebuild
rm -rf dist/ && npm run build

Project Structure

See File Structure for the complete source code layout.


Creating Your First Skill: Implementation Deep Dive

The skill creation workflow is implemented in src/workflows/create-skill-workflow.ts. It orchestrates a multi-step interactive process including name validation, budget checking, trigger configuration, content analysis, and progressive disclosure decomposition.

The Create Skill Workflow Entry Point

The createSkillWorkflow function accepts a SkillStore instance and a scope (user or project), then guides the user through an interactive creation process with validation at each step.

export async function createSkillWorkflow(
  skillStore: SkillStore,
  scope: SkillScope = 'user'
): Promise<void> {
  const scopePath = getSkillsBasePath(scope);
  const scopeLabel = scope === 'user' ? 'user-level' : 'project-level';
  p.intro(pc.bgCyan(pc.black(` Create a New Skill (${scopeLabel}) `)));
  p.log.message(pc.dim(`Target: ${scopePath}`));

  // Step 1: Collect basic info
  const basicInfo = await p.group(
    {
      name: () =>
        p.text({
          message: 'Skill name:',
          placeholder: 'my-skill-name (lowercase, numbers, hyphens)',
          validate: (value) => {
            if (!value) return 'Name is required';
            const suggestion = suggestFixedName(value);
            if (value.length > 64) {
              return suggestion
                ? `Max 64 characters. Suggestion: ${suggestion}`
                : 'Max 64 characters';
            }
            if (!/^[a-z0-9-]+$/.test(value)) {
              return suggestion
                ? `Only lowercase letters, numbers, and hyphens. Suggestion: ${suggestion}`
                : 'Only lowercase letters, numbers, and hyphens allowed';
            }
          },
        }),
      description: () =>
        p.text({
          message: 'Description (what triggers this skill):',
          placeholder: 'Guides X workflow. Use when working with Y or Z.',
          validate: (value) => {
            if (!value) return 'Description is required';
            if (value.length > 1024) return 'Description must be 1024 characters or less';
          },
        }),
      enabled: () =>
        p.confirm({
          message: 'Enable this skill immediately?',
          initialValue: true,
        }),
    },
    { onCancel: () => { p.cancel('Skill creation cancelled'); process.exit(0); } }
  );
}

Skill Metadata Assembly and Validation

After collecting all user input, the workflow builds a complete metadata object with the nested extension structure expected by skill-creator, then validates it with Zod before persisting.

// Build extension data
const ext: GsdSkillCreatorExtension = { enabled };
if (triggers) ext.triggers = triggers;
if (forceOverrideData) ext.forceOverrideReservedName = forceOverrideData;
if (forceOverrideBudgetData) ext.forceOverrideBudget = forceOverrideBudgetData;

// Build metadata with proper nested structure
const metadata: SkillMetadata = {
  name,
  description,
  metadata: {
    extensions: {
      'gsd-skill-creator': ext,
    },
  },
};

// Add argument-hint if detected (SPEC-02)
if (argumentHint) {
  metadata['argument-hint'] = argumentHint;
}

// Validate with Zod for safety
validateSkillInput(metadata);

// Determine if decomposition is needed
const needsDecomposition = disclosureAnalysis.exceedsDecompose
  && disclosureAnalysis.sections.length > 1;

// Create skill (with or without progressive disclosure)
if (needsDecomposition) {
  await skillStore.createWithDisclosure(name, metadata, content as string);
} else {
  await skillStore.create(name, metadata, content as string);
}

The Staging Intake Pipeline

Documents enter the system through the staging intake module (src/staging/intake.ts), which writes both the document and a validated companion metadata file to the staging inbox directory.

export async function stageDocument(options: {
  basePath: string;
  filename: string;
  content: string;
  source: string;
}): Promise<StageDocumentResult> {
  // Create staging directories on first use
  await ensureStagingDirectory(options.basePath);

  // Build target paths
  const documentPath = join(options.basePath, STAGING_DIRS.inbox, options.filename);
  const metadataPath = join(options.basePath, STAGING_DIRS.inbox,
    `${options.filename}.meta.json`);

  // Build and validate metadata
  const metadata: StagingMetadata = {
    submitted_at: new Date().toISOString(),
    source: options.source,
    status: 'inbox' as const,
  };
  const validatedMetadata = StagingMetadataSchema.parse(metadata);

  // Write document and metadata in parallel (independent writes)
  await Promise.all([
    writeFile(documentPath, options.content, 'utf-8'),
    writeFile(metadataPath, JSON.stringify(validatedMetadata, null, 2), 'utf-8'),
  ]);

  return { documentPath, metadataPath };
}

Key APIs Summary

Function Module Purpose
createSkillWorkflow() workflows/create-skill-workflow.ts Interactive skill creation with validation
stageDocument() staging/intake.ts Document intake with Zod-validated metadata
validateSkillInput() validation/skill-validation.ts Zod schema enforcement for skill metadata
suggestFixedName() validation/skill-validation.ts Auto-suggest corrected skill names
ContentAnalyzer disclosure/index.ts Progressive disclosure analysis for large skills