Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

94 changes: 48 additions & 46 deletions scripts/add-lesson-order.ts
Original file line number Diff line number Diff line change
@@ -1,93 +1,95 @@
import * as fs from "node:fs/promises"
import * as path from "node:path"
import matter from "gray-matter"
import * as fs from 'node:fs/promises';
import * as path from 'node:path';
import matter from 'gray-matter';

const PUBLISHED_DIR = path.join(process.cwd(), "content/published/patterns")
const PUBLISHED_DIR = path.join(process.cwd(), 'content/published/patterns');

interface Pattern {
path: string
data: Record<string, unknown>
content: string
path: string;
data: Record<string, unknown>;
content: string;
}

async function findMdxFiles(dir: string): Promise<Pattern[]> {
const entries = await fs.readdir(dir, { withFileTypes: true })
const files: Pattern[] = []
const entries = await fs.readdir(dir, { withFileTypes: true });
const files: Pattern[] = [];

for (const entry of entries) {
const fullPath = path.join(dir, entry.name)
const fullPath = path.join(dir, entry.name);
if (entry.isDirectory()) {
files.push(...(await findMdxFiles(fullPath)))
} else if (entry.isFile() && entry.name.endsWith(".mdx")) {
const content = await fs.readFile(fullPath, "utf-8")
const parsed = matter(content)
files.push(...(await findMdxFiles(fullPath)));
} else if (entry.isFile() && entry.name.endsWith('.mdx')) {
const content = await fs.readFile(fullPath, 'utf-8');
const parsed = matter(content);
files.push({
path: fullPath,
data: parsed.data as Record<string, unknown>,
content: parsed.content,
})
});
}
}
return files
return files;
}

function getSkillLevel(data: Record<string, unknown>): string {
return ((data.skillLevel || data.skill || "intermediate") as string).toLowerCase()
return (
(data.skillLevel || data.skill || 'intermediate') as string
).toLowerCase();
}

async function main() {
const allPatterns = await findMdxFiles(PUBLISHED_DIR)
const allPatterns = await findMdxFiles(PUBLISHED_DIR);

// Group by directory (Application Pattern)
const byDir = new Map<string, Pattern[]>()
const byDir = new Map<string, Pattern[]>();
for (const p of allPatterns) {
const relPath = path.relative(PUBLISHED_DIR, p.path)
const dir = relPath.split(path.sep)[0]
if (!byDir.has(dir)) byDir.set(dir, [])
byDir.get(dir)?.push(p)
const relPath = path.relative(PUBLISHED_DIR, p.path);
const dir = relPath.split(path.sep)[0];
if (!byDir.has(dir)) byDir.set(dir, []);
byDir.get(dir)?.push(p);
}

let updated = 0
for (const [dir, patterns] of byDir) {
let updated = 0;

for (const [_dir, patterns] of byDir) {
// Group by skill level within each directory
const bySkill = new Map<string, Pattern[]>()
const bySkill = new Map<string, Pattern[]>();
for (const p of patterns) {
const skill = getSkillLevel(p.data)
if (!bySkill.has(skill)) bySkill.set(skill, [])
bySkill.get(skill)?.push(p)
const skill = getSkillLevel(p.data);
if (!bySkill.has(skill)) bySkill.set(skill, []);
bySkill.get(skill)?.push(p);
}

// Assign lessonOrder within each skill level
for (const [skill, skillPatterns] of bySkill) {
for (const [_skill, skillPatterns] of bySkill) {
// Sort alphabetically by title for consistent ordering
skillPatterns.sort((a, b) => {
const titleA = (a.data.title as string) || ""
const titleB = (b.data.title as string) || ""
return titleA.localeCompare(titleB)
})
const titleA = (a.data.title as string) || '';
const titleB = (b.data.title as string) || '';
return titleA.localeCompare(titleB);
});

let order = 1
let order = 1;
for (const p of skillPatterns) {
// Skip if already has lessonOrder
if (p.data.lessonOrder !== undefined) {
order++
continue
order++;
continue;
}

// Add lessonOrder
p.data.lessonOrder = order
order++
p.data.lessonOrder = order;
order++;

// Write back
const newContent = matter.stringify(p.content, p.data)
await fs.writeFile(p.path, newContent)
updated++
const newContent = matter.stringify(p.content, p.data);
await fs.writeFile(p.path, newContent);
updated++;
}
}
}

console.log(`Updated ${updated} patterns with lessonOrder`)
console.log(`Updated ${updated} patterns with lessonOrder`);
}

main().catch(console.error)
main().catch(console.error);
2 changes: 1 addition & 1 deletion scripts/autofix/prepublish-autofix.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,11 @@
* Note: This is a scaffold. No file mutations are performed yet.
*/

import dotenv from 'dotenv';
import { exec as _exec } from 'node:child_process';
import fs from 'node:fs/promises';
import path from 'node:path';
import { promisify } from 'node:util';
import dotenv from 'dotenv';

// Style gate uses Biome via bunx

Expand Down
26 changes: 13 additions & 13 deletions scripts/generate-skills.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,21 @@
* Generate Skills (Claude, Gemini, OpenAI) from published Effect patterns
*/

import * as fs from "node:fs/promises";
import * as path from "node:path";
import * as fs from 'node:fs/promises';
import * as path from 'node:path';
import {
readPattern,
groupPatternsByCategory,
generateCategorySkill,
writeSkill,
generateGeminiSkill,
writeGeminiSkill,
generateOpenAISkill,
groupPatternsByCategory,
readPattern,
writeGeminiSkill,
writeOpenAISkill,
} from "../packages/cli/src/skills/skill-generator";
writeSkill,
} from '../packages/cli/src/skills/skill-generator.js';

const PROJECT_ROOT = process.cwd();
const PATTERNS_DIR = path.join(PROJECT_ROOT, "content/published/patterns");
const PATTERNS_DIR = path.join(PROJECT_ROOT, 'content/published/patterns');

async function findMdxFiles(dir: string): Promise<string[]> {
const mdxFiles: string[] = [];
Expand All @@ -27,7 +27,7 @@ async function findMdxFiles(dir: string): Promise<string[]> {
if (entry.isDirectory()) {
const subFiles = await findMdxFiles(fullPath);
mdxFiles.push(...subFiles);
} else if (entry.name.endsWith(".mdx")) {
} else if (entry.name.endsWith('.mdx')) {
mdxFiles.push(fullPath);
}
}
Expand All @@ -36,10 +36,10 @@ async function findMdxFiles(dir: string): Promise<string[]> {
}

async function main() {
console.log("\n🎓 Generating Skills from Effect Patterns\n");
console.log('\n🎓 Generating Skills from Effect Patterns\n');

// Read all pattern files
console.log("📖 Reading published patterns...");
console.log('📖 Reading published patterns...');
const mdxFiles = await findMdxFiles(PATTERNS_DIR);
console.log(`✓ Found ${mdxFiles.length} patterns\n`);

Expand All @@ -58,12 +58,12 @@ async function main() {
console.log(`✓ Parsed ${patterns.length} patterns\n`);

// Group by category
console.log("🗂️ Grouping patterns by category...");
console.log('🗂️ Grouping patterns by category...');
const categoryMap = groupPatternsByCategory(patterns);
console.log(`✓ Found ${categoryMap.size} categories\n`);

// Generate all skills
console.log("📝 Generating skills...\n");
console.log('📝 Generating skills...\n');

let claudeCount = 0;
let geminiCount = 0;
Expand Down
32 changes: 17 additions & 15 deletions scripts/ingest-discord.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { NodeContext, NodeRuntime } from '@effect/platform-node';
import { FileSystem } from '@effect/platform/FileSystem';
import { NodeContext, NodeRuntime } from '@effect/platform-node';
import { Config, Effect, Layer, Logger, LogLevel } from 'effect';
import {
Discord,
Expand Down Expand Up @@ -44,20 +44,22 @@ const program = Effect.gen(function* () {
const userIdMap = new Map<string, string>();
let userCounter = 1;

const anonymizedMessages = channelExport.messages.map((message: Record<string, unknown>) => {
const authorId = (message.author as Record<string, unknown>).id as string;
if (!userIdMap.has(authorId)) {
userIdMap.set(authorId, `user_${userCounter++}`);
}
return {
...message,
author: {
...(message.author as Record<string, unknown>),
id: userIdMap.get(authorId),
name: userIdMap.get(authorId),
},
};
});
const anonymizedMessages = channelExport.messages.map(
(message: Record<string, unknown>) => {
const authorId = (message.author as Record<string, unknown>).id as string;
if (!userIdMap.has(authorId)) {
userIdMap.set(authorId, `user_${userCounter++}`);
}
return {
...message,
author: {
...(message.author as Record<string, unknown>),
id: userIdMap.get(authorId),
name: userIdMap.get(authorId),
},
};
},
);

// Define the output path for the curated dataset.
const outputPath = 'content/discord/beginner-questions.json';
Expand Down
5 changes: 3 additions & 2 deletions scripts/ingest/ingest-pipeline-improved.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,11 @@
* 8. Reporting - Generate detailed report
*/

import matter from 'gray-matter';
import { exec } from 'node:child_process';
import * as fs from 'node:fs/promises';
import * as path from 'node:path';
import { promisify } from 'node:util';
import matter from 'gray-matter';

const execAsync = promisify(exec);

Expand Down Expand Up @@ -217,7 +217,8 @@ async function validatePatterns(
const warnCount = result.issues.filter((i) => i.type === 'warning').length;

console.log(
`${status} ${pattern.id} ${errorCount > 0 ? colorize(`(${errorCount} errors)`, 'red') : ''
`${status} ${pattern.id} ${
errorCount > 0 ? colorize(`(${errorCount} errors)`, 'red') : ''
} ${warnCount > 0 ? colorize(`(${warnCount} warnings)`, 'yellow') : ''}`,
);

Expand Down
20 changes: 8 additions & 12 deletions scripts/ingest/populate-expectations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,6 @@ import { Console, Context, Data, Effect, Layer } from 'effect';
import { MdxService } from 'effect-mdx';

// --- Configuration Service (Idiomatic Effect.Service pattern) ---
// Define the AppConfig interface
interface AppConfigService {
readonly srcDir: string;
readonly processedDir: string;
}

// Create the AppConfig service using Effect.Service pattern
// eslint-disable-next-line @typescript-eslint/no-unused-vars
class AppConfig extends Effect.Service<AppConfig>()('AppConfig', {
Expand All @@ -19,7 +13,7 @@ class AppConfig extends Effect.Service<AppConfig>()('AppConfig', {
processedDir:
process.env.PROCESSED_DIR || `${process.cwd()}/content/new/processed`,
}),
}) { }
}) {}

// The AppConfigLive layer is now available as AppConfig.Default

Expand All @@ -33,7 +27,7 @@ class ExpectationPrompt extends Data.TaggedClass('ExpectationPrompt')<{
readonly actualStderr: string;
readonly actualErrorDetail: string; // Error message from execAsync if it threw
readonly executionStatus: 'success' | 'failure';
}> { }
}> {}

// Structured output expected from the LLM using Data.TaggedClass
class GeneratedExpectations extends Data.TaggedClass('GeneratedExpectations')<{
Expand All @@ -42,7 +36,7 @@ class GeneratedExpectations extends Data.TaggedClass('GeneratedExpectations')<{
readonly reasoning: string; // LLM's explanation for its decision
readonly discrepancyFlag: boolean; // True if actuals didn't align with pattern intent
readonly discrepancyReason?: string; // Why it didn't align
}> { }
}> {}

// LLMService Tag (represents the dependency context for accessing the service)
class LLMService extends Context.Tag('LLMService')<
Expand All @@ -52,7 +46,7 @@ class LLMService extends Context.Tag('LLMService')<
prompt: ExpectationPrompt,
) => Effect.Effect<GeneratedExpectations, Error, never>; // LLM-related errors
}
>() { }
>() {}

// Live implementation for LLMService (SIMULATED for demonstration)
// This adheres to the service interface and returns an Effect.
Expand All @@ -65,7 +59,8 @@ const LLMLive = Layer.succeed(
// Log the processing
return Effect.succeed(
Console.info(
`[LLM Sim] Processing prompt for status: ${prompt.executionStatus
`[LLM Sim] Processing prompt for status: ${
prompt.executionStatus
} for pattern ${prompt.patternMdxContent
.split('\n')[0]
.substring(0, 50)}...`,
Expand Down Expand Up @@ -205,7 +200,8 @@ const processPatternFile = (mdxFilePath: string) =>
actualStdout = executionResult.stdout.trim(); // Still capture stdout/stderr from child process if available on failure
actualStderr = executionResult.stderr.trim();
yield* Console.error(
` Execution of ${baseName}.ts failed: ${actualErrorDetail.split('\n')[0]
` Execution of ${baseName}.ts failed: ${
actualErrorDetail.split('\n')[0]
}`,
);
}
Expand Down
5 changes: 4 additions & 1 deletion scripts/ingest/process.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,10 @@ function parseMdx(
return { frontmatter, content };
}

function validateFrontMatter(filePath: string, fm: Record<string, unknown>): FrontMatter {
function validateFrontMatter(
filePath: string,
fm: Record<string, unknown>,
): FrontMatter {
const required = ['id', 'title', 'skillLevel', 'useCase', 'summary'];
for (const key of required) {
if (!fm[key])
Expand Down
Loading
Loading