Skip to content
Open
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
15 changes: 15 additions & 0 deletions .ralph/activity.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,3 +94,18 @@
- ❌ npm run build

---
### Iteration 1 - 2026-02-20T17:56:38.440Z

**Status:** 🔶 Partial
**Summary:** The core architecture setup section already covers the scaffolding that was needed for this task. Le
**Duration:** 4m 15s
**Cost:** $0.627 (42,399 tokens)

---
### Iteration 2 - 2026-02-20T18:00:53.634Z

**Status:** 🚫 Blocked
**Summary:** Permission denied
**Duration:** 5m 0s

---
4 changes: 4 additions & 0 deletions .ralph/iteration-log.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,7 @@
- Status: validation passed
- Changes: yes
- Summary: Process timed out
## Iteration 1
- Status: validation passed
- Changes: yes
- Summary: The core architecture setup section already covers the scaffolding that was needed for this task. Le
54 changes: 25 additions & 29 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,49 +12,44 @@
"types": "./dist/index.d.ts"
},
"./vite": {
"import": "./dist/plugins/vite.mjs",
"require": "./dist/plugins/vite.js",
"types": "./dist/plugins/vite.d.ts"
"import": "./dist/vite.mjs",
"require": "./dist/vite.js",
"types": "./dist/vite.d.ts"
},
"./next": {
"import": "./dist/plugins/next.mjs",
"require": "./dist/plugins/next.js",
"types": "./dist/plugins/next.d.ts"
"import": "./dist/next.mjs",
"require": "./dist/next.js",
"types": "./dist/next.d.ts"
},
"./astro": {
"import": "./dist/plugins/astro.mjs",
"require": "./dist/plugins/astro.js",
"types": "./dist/plugins/astro.d.ts"
"import": "./dist/astro.mjs",
"require": "./dist/astro.js",
"types": "./dist/astro.d.ts"
},
"./nuxt": {
"import": "./dist/plugins/nuxt.mjs",
"require": "./dist/plugins/nuxt.js",
"types": "./dist/plugins/nuxt.d.ts"
"import": "./dist/nuxt.mjs",
"require": "./dist/nuxt.js",
"types": "./dist/nuxt.d.ts"
},
"./webpack": {
"import": "./dist/plugins/webpack.mjs",
"require": "./dist/plugins/webpack.js",
"types": "./dist/plugins/webpack.d.ts"
"import": "./dist/webpack.mjs",
"require": "./dist/webpack.js",
"types": "./dist/webpack.d.ts"
},
"./react": {
"import": "./dist/widget/react.mjs",
"require": "./dist/widget/react.js",
"types": "./dist/widget/react.d.ts"
"import": "./dist/react.mjs",
"require": "./dist/react.js",
"types": "./dist/react.d.ts"
},
"./vue": {
"import": "./dist/widget/vue.mjs",
"require": "./dist/widget/vue.js",
"types": "./dist/widget/vue.d.ts"
},
"./svelte": {
"import": "./dist/widget/svelte.mjs",
"require": "./dist/widget/svelte.js",
"types": "./dist/widget/svelte.d.ts"
"import": "./dist/vue.mjs",
"require": "./dist/vue.js",
"types": "./dist/vue.d.ts"
},
"./widget": {
"import": "./dist/widget/core.mjs",
"require": "./dist/widget/core.js",
"types": "./dist/widget/core.d.ts"
"import": "./dist/widget.mjs",
"require": "./dist/widget.js",
"types": "./dist/widget.d.ts"
}
},
"files": [
Expand All @@ -64,6 +59,7 @@
"build": "tsup",
"dev": "tsup --watch",
"test": "vitest",
"lint": "tsc --noEmit",
"prepublishOnly": "npm run build"
},
"keywords": [
Expand Down
135 changes: 135 additions & 0 deletions src/core/ai-index.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
import { describe, it, expect, beforeEach, vi } from 'vitest';
import { generateAiIndex } from './ai-index';
import * as fs from 'fs/promises';
import * as path from 'path';

vi.mock('fs/promises');
vi.mock('./utils', () => ({
resolveConfig: vi.fn().mockResolvedValue({
routes: [
{ path: '/', title: 'Home', description: 'Homepage' },
{ path: '/about', title: 'About', description: 'About us' },
{ path: '/contact', title: 'Contact' }
],
baseUrl: 'https://example.com'
}),
ensureDir: vi.fn().mockResolvedValue(undefined)
}));

describe('generateAiIndex', () => {
beforeEach(() => {
vi.clearAllMocks();
});

it('should generate AI index with content extraction', async () => {
const mockReadFile = vi.mocked(fs.readFile);
const mockWriteFile = vi.mocked(fs.writeFile);

mockReadFile.mockImplementation((filePath) => {
if (filePath.toString().endsWith('/public/index.html')) {
return Promise.resolve(Buffer.from(`
<!DOCTYPE html>
<html>
<head><title>Test Page</title></head>
<body>
<h1>Welcome</h1>
<p>This is the homepage content.</p>
</body>
</html>
`));
}
return Promise.resolve(Buffer.from('<html></html>'));
});

await generateAiIndex('/test/project');

expect(mockWriteFile).toHaveBeenCalledWith(
path.join('/test/project', 'public', 'ai-index.json'),
expect.any(String)
);

const writtenContent = JSON.parse(mockWriteFile.mock.calls[0][1] as string);
expect(writtenContent).toHaveProperty('version', '1.0.0');
expect(writtenContent).toHaveProperty('generated');
expect(writtenContent).toHaveProperty('baseUrl', 'https://example.com');
expect(writtenContent.pages).toHaveLength(3);
expect(writtenContent.pages[0]).toMatchObject({
url: 'https://example.com/',
title: 'Home',
description: 'Homepage'
});
});

it('should generate AI index without content when extraction fails', async () => {
const mockReadFile = vi.mocked(fs.readFile);
const mockWriteFile = vi.mocked(fs.writeFile);

mockReadFile.mockRejectedValue(new Error('File not found'));

await generateAiIndex('/test/project');

expect(mockWriteFile).toHaveBeenCalled();
const writtenContent = JSON.parse(mockWriteFile.mock.calls[0][1] as string);

expect(writtenContent.pages[0]).toMatchObject({
url: 'https://example.com/',
title: 'Home',
description: 'Homepage'
});
expect(writtenContent.pages[0].content).toBeUndefined();
});

it('should handle empty routes gracefully', async () => {
const { resolveConfig } = await import('./utils');
vi.mocked(resolveConfig).mockResolvedValueOnce({
routes: [],
baseUrl: 'https://example.com'
});

const mockWriteFile = vi.mocked(fs.writeFile);

await generateAiIndex('/test/project');

expect(mockWriteFile).toHaveBeenCalled();
const writtenContent = JSON.parse(mockWriteFile.mock.calls[0][1] as string);
expect(writtenContent.pages).toEqual([]);
});

it('should extract content from HTML correctly', async () => {
const mockReadFile = vi.mocked(fs.readFile);
const mockWriteFile = vi.mocked(fs.writeFile);

mockReadFile.mockResolvedValue(Buffer.from(`
<!DOCTYPE html>
<html>
<head>
<title>Product Page</title>
<meta name="description" content="Learn about our products">
</head>
<body>
<nav>Navigation links</nav>
<main>
<h1>Our Products</h1>
<p>We offer amazing solutions.</p>
<article>
<h2>Product A</h2>
<p>Description of Product A with features.</p>
</article>
</main>
<footer>Footer content</footer>
</body>
</html>
`));

await generateAiIndex('/test/project');

const writtenContent = JSON.parse(mockWriteFile.mock.calls[0][1] as string);
const page = writtenContent.pages[0];

expect(page.content).toContain('Our Products');
expect(page.content).toContain('amazing solutions');
expect(page.content).toContain('Product A');
expect(page.content).not.toContain('Navigation links');
expect(page.content).not.toContain('Footer content');
});
});
6 changes: 3 additions & 3 deletions src/core/ai-index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { readdirSync, readFileSync, statSync } from 'fs';
import { join, relative, extname } from 'path';
import { createHash } from 'crypto';
import type { ResolvedConfig, AIIndexEntry } from '../types';
import type { ResolvedAeoConfig, AIIndexEntry } from '../types';
import { parseFrontmatter, extractTitle } from './utils';

function extractKeywords(content: string): string[] {
Expand Down Expand Up @@ -44,7 +44,7 @@ function chunkContent(content: string, maxLength: number = 2000): string[] {
return chunks;
}

function collectAIIndexEntries(dir: string, config: ResolvedConfig, base: string = dir): AIIndexEntry[] {
function collectAIIndexEntries(dir: string, config: ResolvedAeoConfig, base: string = dir): AIIndexEntry[] {
const entries: AIIndexEntry[] = [];

try {
Expand Down Expand Up @@ -97,7 +97,7 @@ function collectAIIndexEntries(dir: string, config: ResolvedConfig, base: string
return entries;
}

export function generateAIIndex(config: ResolvedConfig): string {
export function generateAIIndex(config: ResolvedAeoConfig): string {
const entries = collectAIIndexEntries(config.contentDir, config);

const index = {
Expand Down
2 changes: 1 addition & 1 deletion src/core/detect.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { Framework, FrameworkInfo } from '../types';
import type { FrameworkType, FrameworkInfo } from '../types';
import { readPackageJson } from './utils';

export function detectFramework(projectRoot: string = process.cwd()): FrameworkInfo {
Expand Down
Loading