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 |

