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
8 changes: 7 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
# Make these targets phony (they don't create files with these names)
.PHONY: all setup dev clean clean-all build build-win build-linux \
build-mac build-mac-arm build-mac-universal \
test css css-watch lint format validate qa setup-hooks sonar \
test css css-watch lint lint-md format validate qa docs-screenshots setup-hooks sonar \
security gitleaks sbom renovate renovate-local mend-scan \
icons sample-logo release

Expand Down Expand Up @@ -59,6 +59,9 @@ css-watch: setup-scripts
lint: setup-scripts
@node scripts/index.js lint

lint-md: setup-scripts
@node scripts/index.js lint-md

format: setup-scripts
@node scripts/index.js format

Expand All @@ -68,6 +71,9 @@ validate: setup-scripts
qa: setup-scripts
@node scripts/index.js qa

docs-screenshots: setup-scripts
@node scripts/index.js docs-screenshots

setup-hooks: setup-scripts
@node scripts/index.js hooks

Expand Down
40 changes: 40 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,47 @@ A desktop app to prepare code repositories for AI workflows.
- File filtering with custom patterns and `.gitignore` support
- Token counting support for selected files
- Processed output ready to copy/export for AI tools
- Export format selector: Markdown or XML
- Cross-platform support (Windows, macOS, Linux)
- UI panel screenshots: `docs/APP_VIEWS.md`

## Processed Output Example

![Processed Output panel](docs/images/app-processed-panel.png)

Full sample files:

- Markdown: [`docs/examples/output-markdown.md`](docs/examples/output-markdown.md)
- XML: [`docs/examples/output.xml`](docs/examples/output.xml)

### Markdown export example

````md
# Repository Analysis

## src/App.tsx

```ts
export function App() {
return <main>Hello AI Code Fusion</main>;
}
```

Tokens: 120
````

### XML export example

```xml
<?xml version="1.0" encoding="UTF-8"?>
<repository totalFiles="1" totalTokens="120">
<file path="src/App.tsx" tokens="120"><![CDATA[
export function App() {
return <main>Hello AI Code Fusion</main>;
}
]]></file>
</repository>
```

## Download Release

Expand Down
Binary file removed assets/ai_code_fusion_1.jpg
Binary file not shown.
Binary file removed assets/ai_code_fusion_2.jpg
Binary file not shown.
Binary file removed assets/ai_code_fusion_3.jpg
Binary file not shown.
Binary file removed assets/ai_code_fusion_4.jpg
Binary file not shown.
31 changes: 31 additions & 0 deletions docs/APP_VIEWS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# App Views

This page shows up-to-date screenshots for the main app panels.

## Start Panel (Config)

![Start panel with filtering and output options](images/app-config-panel.png)

## Select Files Panel

![Select Files panel with repository tree](images/app-select-panel.png)

## Select Files Panel (With Selection)

![Select Files panel after choosing files](images/app-select-panel-selected.png)

## Select Files Panel (Resized)

![Select Files panel in resized viewport](images/app-select-panel-resized.png)

## Processed Output Panel

![Processed Output panel with generated content and token table](images/app-processed-panel.png)

## Refresh Screenshots

```bash
npm run docs:screenshots
```

This command runs the Playwright capture flow and updates screenshots in `docs/images/`.
2 changes: 2 additions & 0 deletions docs/DEVELOPMENT.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,11 @@ make build-mac
# Quality
make test
make lint
make lint-md
make format
make validate
make qa
make docs-screenshots

# Security / dependency automation
make security
Expand Down
23 changes: 23 additions & 0 deletions docs/examples/output-markdown.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Repository Analysis

## src/App.tsx

```ts
export function App() {
return <main>Hello AI Code Fusion</main>;
}
```

Tokens: 120

## src/features/feature-24/ui/Feature24Panel.tsx

```ts
export function Feature24Panel() {
return <section>Panel content</section>;
}
```

Tokens: 240

--END--
13 changes: 13 additions & 0 deletions docs/examples/output.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<repository totalFiles="2" totalTokens="360">
<file path="src/App.tsx" tokens="120"><![CDATA[
export function App() {
return <main>Hello AI Code Fusion</main>;
}
]]></file>
<file path="src/features/feature-24/ui/Feature24Panel.tsx" tokens="240"><![CDATA[
export function Feature24Panel() {
return <section>Panel content</section>;
}
]]></file>
</repository>
Binary file added docs/images/app-config-panel.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/images/app-processed-panel.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/images/app-select-panel-resized.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/images/app-select-panel-selected.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/images/app-select-panel.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
5 changes: 4 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@
"predev": "npm run build:ts && node scripts/clean-dev-assets.js",
"dev": "node scripts/index.js dev",
"clear-assets": "rimraf src/renderer/bundle.js src/renderer/bundle.js.map src/renderer/bundle.js.LICENSE.txt src/renderer/output.css",
"lint": "cross-env ESLINT_USE_FLAT_CONFIG=false eslint src tests --ext .js,.jsx,.ts,.tsx --cache",
"lint": "cross-env ESLINT_USE_FLAT_CONFIG=false eslint src tests --ext .js,.jsx,.ts,.tsx --cache && npm run lint:md",
"lint:md": "node scripts/lint-markdown-links.js",
"lint:tests": "cross-env ESLINT_USE_FLAT_CONFIG=false eslint tests --ext .js,.jsx,.ts,.tsx --cache",
"format": "prettier --write \"**/*.{js,jsx,ts,tsx,json,md,html,css}\"",
"test": "jest --config jest.config.js --passWithNoTests",
Expand All @@ -33,6 +34,8 @@
"qa": "node scripts/index.js qa",
"preqa:screenshot": "npm run build:ts",
"qa:screenshot": "node scripts/capture-ui-screenshot.js",
"predocs:screenshots": "npm run build:ts && npm run build:webpack",
"docs:screenshots": "node scripts/generate-doc-screenshots.js",
Comment on lines +37 to +38

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Action required

1. Docs screenshots miss css build 🐞 Bug ⛯ Reliability

predocs:screenshots builds TS + webpack, but does not build src/renderer/output.css even
  though the renderer HTML always loads it.
• On a clean checkout or after CSS changes, npm run docs:screenshots can generate
  unstyled/misleading screenshots (or stale styling), undermining the purpose of the docs images.
Agent Prompt
### Issue description
`npm run docs:screenshots` can run without producing/updating `src/renderer/output.css`, even though the renderer HTML depends on it. This can yield unstyled or stale UI screenshots.

### Issue Context
`src/renderer/index.html` loads `output.css`, and the dev workflow treats CSS generation as a separate step.

### Fix Focus Areas
- package.json[29-37]
- scripts/generate-doc-screenshots.js[26-43]

### Suggested change
Update `predocs:screenshots` to include CSS build, e.g.
- `"predocs:screenshots": "npm run build:ts && npm run build:css && npm run build:webpack"`

(Alternatively/additionally) have `generate-doc-screenshots.js` call `npm run build:css` before spawning the capture script.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

"security": "node scripts/index.js security",
"gitleaks": "node scripts/index.js gitleaks",
"gitleaks:staged": "node scripts/index.js gitleaks-staged",
Expand Down
119 changes: 105 additions & 14 deletions scripts/capture-ui-screenshot.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ const { chromium } = require('playwright');

const ROOT_DIR = path.join(__dirname, '..');
const RENDERER_DIR = path.join(ROOT_DIR, 'src', 'renderer');
const SCREENSHOT_DIR = path.join(ROOT_DIR, 'dist', 'qa', 'screenshots');
const DEFAULT_SCREENSHOT_DIR = path.join('dist', 'qa', 'screenshots');
const SCREENSHOT_DIR = resolveOutputDirectory(process.env.UI_SCREENSHOT_DIR);
const PORT = Number(process.env.UI_SCREENSHOT_PORT || 4173);
const DEFAULT_SCREENSHOT_NAME = `ui-${process.platform}-${process.arch}.png`;
const FIXED_MTIME = 1700000000000;
Expand Down Expand Up @@ -58,6 +59,21 @@ function sanitizeScreenshotName(nameCandidate) {
return withExtension;
}

function resolveOutputDirectory(dirCandidate) {
const rawDir =
typeof dirCandidate === 'string' && dirCandidate.trim()
? dirCandidate.trim()
: DEFAULT_SCREENSHOT_DIR;
const absoluteDir = path.resolve(ROOT_DIR, rawDir);
const relativeToRoot = path.relative(ROOT_DIR, absoluteDir);

if (relativeToRoot.startsWith('..') || path.isAbsolute(relativeToRoot)) {
throw new Error(`Invalid screenshot directory: ${rawDir}`);
}

return absoluteDir;
}

function resolveOutputPath(fileName) {
const targetPath = path.resolve(SCREENSHOT_DIR, fileName);
const relativeToRoot = path.relative(SCREENSHOT_DIR, targetPath);
Expand Down Expand Up @@ -313,12 +329,14 @@ const SCREENSHOTS = {
sourceTab: resolveOutputPath(`${SCREENSHOT_BASE_NAME}-source.png`),
sourceSelected: resolveOutputPath(`${SCREENSHOT_BASE_NAME}-source-selected.png`),
sourceSelectedResized: resolveOutputPath(`${SCREENSHOT_BASE_NAME}-source-selected-resized.png`),
processedTab: resolveOutputPath(`${SCREENSHOT_BASE_NAME}-processed.png`),
};

const UI_SELECTORS = {
appRoot: '#app',
configTab: '[data-tab="config"]',
sourceTab: '[data-tab="source"]',
processedTabActive: '[data-tab="processed"][aria-selected="true"]',
secretScanningToggle: '#enable-secret-scanning',
suspiciousFilesToggle: '#exclude-suspicious-files',
sourceFolderExpandButton: 'button[aria-label="Expand folder src"]',
Expand All @@ -331,6 +349,7 @@ const UI_SELECTORS = {
refreshFileListButton: 'button[title="Refresh the file list"]',
fileTreeScrollContainer: '.file-tree .overflow-auto',
processSelectedFilesButton: '[data-testid="process-selected-files-button"]',
processedContent: '#processed-content',
};

async function setupMockElectronApi(page) {
Expand Down Expand Up @@ -358,20 +377,65 @@ async function setupMockElectronApi(page) {
const tree = excludeSensitiveFiles ? mockFilteredDirectoryTree : mockDirectoryTree;
return cloneTree(tree);
},
analyzeRepository: async () => ({
totalFiles: 0,
totalTokens: 0,
files: [],
}),
processRepository: async () => ({
content: '',
stats: {
totalFiles: 0,
totalTokens: 0,
analyzeRepository: async (options = {}) => {
const selectedFilePaths = Array.isArray(options?.selectedFiles) ? options.selectedFiles : [];
const filesInfo = selectedFilePaths.map((filePath, index) => {
const normalizedPath = String(filePath);
const relativePath = normalizedPath.startsWith(`${mockRootPath}/`)
? normalizedPath.slice(mockRootPath.length + 1)
: normalizedPath;
return {
Comment on lines +380 to +387

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (bug_risk): Path prefix stripping in analyzeRepository is not Windows-safe.

Using normalizedPath.startsWith(${mockRootPath}/) bakes in a forward slash and will fail when mockRootPath contains backslashes on Windows. Instead, derive the relative path with path.relative(mockRootPath, normalizedPath) and use that (e.g., rejecting results that start with '..' or are absolute), so this logic stays path-separator agnostic and works on all platforms, including for QA/docs screenshot generation on Windows.

path: relativePath,
tokens: 120 * (index + 1),
isBinary: false,
};
});

return {
totalFiles: filesInfo.length,
totalTokens: filesInfo.reduce((sum, file) => sum + file.tokens, 0),
filesInfo,
};
},
processRepository: async (options = {}) => {
const inputFilesInfo = Array.isArray(options?.filesInfo) ? options.filesInfo : [];
const filesInfo = inputFilesInfo.map((file, index) => ({
path: String(file?.path || `src/file-${index + 1}.ts`),
tokens:
Number.isFinite(file?.tokens) && Number(file.tokens) > 0 ? Number(file.tokens) : 120 * (index + 1),
isBinary: false,
}));
const totalTokens = filesInfo.reduce((sum, file) => sum + file.tokens, 0);
const exportFormat = options?.options?.exportFormat === 'xml' ? 'xml' : 'markdown';
const content =
exportFormat === 'xml'
? [
'<?xml version="1.0" encoding="UTF-8"?>',
`<repository totalFiles="${filesInfo.length}" totalTokens="${totalTokens}">`,
...filesInfo.map(
(file) =>
` <file path="${file.path}" tokens="${file.tokens}"><![CDATA[// Preview for ${file.path}]]></file>`
),
'</repository>',
].join('\n')
: [
'# Repository Analysis',
'',
...filesInfo.map(
(file) => `## ${file.path}\n\n\`\`\`ts\n// Preview for ${file.path}\n\`\`\`\nTokens: ${file.tokens}\n`

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The mock data generation for the markdown format includes a trailing newline (\n) at the end of each file's section. When these sections are joined by \n, it results in an extra blank line between each file's output in the final mock content. Removing the trailing newline will produce a more cleanly formatted output.

Suggested change
(file) => `## ${file.path}\n\n\`\`\`ts\n// Preview for ${file.path}\n\`\`\`\nTokens: ${file.tokens}\n`
(file) => `## ${file.path}\n\n\`\`\`ts\n// Preview for ${file.path}\n\`\`\`\nTokens: ${file.tokens}`

),
'--END--',
].join('\n');

return {
content,
exportFormat,
totalTokens,
processedFiles: filesInfo.length,
skippedFiles: 0,
processedFiles: 0,
},
}),
filesInfo,
};
},
countFilesTokens: async (options) => {
const filePaths = Array.isArray(options?.filePaths) ? options.filePaths : [];
const results = {};
Expand Down Expand Up @@ -569,6 +633,33 @@ async function captureAppStateScreenshots(page) {
await runStep('Capture resized screenshot with deep tree expanded', async () => {
await page.screenshot({ path: SCREENSHOTS.sourceSelectedResized, fullPage: true });
});

await runStep('Return to desktop viewport before processing', async () => {
await page.setViewportSize({ width: 1440, height: 900 });
});

await runStep('Wait for process button to be enabled', async () => {
await page.waitForFunction((selector) => {
const button = document.querySelector(selector);
if (!(button instanceof HTMLButtonElement)) {
return false;
}
return !button.disabled && /process selected files/i.test(button.textContent || '');
}, UI_SELECTORS.processSelectedFilesButton);
});

await runStep('Process selected files', async () => {
await page.locator(UI_SELECTORS.processSelectedFilesButton).first().click();
});

await runStep('Wait for processed panel to render', async () => {
await page.waitForSelector(UI_SELECTORS.processedTabActive, { timeout: 10000 });
await page.waitForSelector(UI_SELECTORS.processedContent, { timeout: 10000 });
});

await runStep('Capture processed tab screenshot', async () => {
await page.screenshot({ path: SCREENSHOTS.processedTab, fullPage: true });
});
}

async function captureScreenshot() {
Expand Down
Loading
Loading