Skip to content

Conversation

@cs-util
Copy link

@cs-util cs-util commented Oct 31, 2025

This pull request introduces a comprehensive automation and quality framework for the project, focusing on enabling autonomous AI agents (such as Claude and Cursor) to contribute via GitHub issues and pull requests, while enforcing high quality standards. The changes include new CI workflows, pre-push hooks, and detailed issue templates for agent-driven development, bug reports, and feature requests.

The most important changes are:

Automation and Agent Integration:

  • Added GitHub Actions workflows for Claude and Cursor agents, allowing issues to trigger autonomous coding agents that create branches, implement requirements, and open pull requests. These workflows include status reporting and enforce strict validation and quality checks. (.github/workflows/claude-agent.yml, .github/workflows/cursor-agent.yml) [1] [2]
  • Introduced an agent-specific PR validation workflow that ensures agent-created pull requests reference an issue, use the PR template, and pass all CI checks before being marked as ready for review. (.github/workflows/ci.yml)

Continuous Integration and Quality Gates:

  • Established a robust CI pipeline that runs core tests, uploads coverage and mutation reports, and enforces quality gates such as linting, duplication, cycle, and dependency checks. (.github/workflows/ci.yml)
  • Added a pre-push git hook to block code pushes unless the full test suite passes locally, preventing untested code from entering the repository. (.githooks/pre-push)

Issue and PR Templates for Agent-Driven Development:

  • Added detailed issue templates for agent tasks, bug reports, and feature requests, each specifying objectives, requirements, testing strategies, and acceptance criteria tailored for autonomous agents. These templates guide both human and AI contributors to ensure clarity, quality, and traceability. (.github/ISSUE_TEMPLATE/agent_task.md, .github/ISSUE_TEMPLATE/bug_report.md, .github/ISSUE_TEMPLATE/feature_request.md) [1] [2] [3]

Tooling and Configuration:

  • Introduced a root-level Babel configuration pointer file to ensure compatibility with tools like Jest and maintain centralized config management. (.babelrc.js)

…nvironment's built-in window global).

Removed the unused node parameter from the smartDel rule's replacement function (this fixed the no-unused-vars error).
In html-export-helper.js
Removed the prior /* global window, DOMPurify */ and replaced it with /* global DOMPurify */ to declare DOMPurify as a known global without re-declaring window
…c into separate functions for improved readability and maintainability.
html-export-helper.test.js — tests sanitization, dark styles, and the DOMPurify branch.
converter.extra.test.js and converter.more.test.js — tests for footnotes, tables, smartDel heuristic, ordered lists, custom list newline handling, empty-table behavior, and a roundtrip markdown → HTML → markdown.
I converted the src modules to CommonJS-only modules to avoid unreachable browser-only UMD branches being counted as uncovered in Node/Jest
Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +1 to +12
// @ts-check
import { defineConfig, devices } from '@playwright/test';

/**
* Playwright configuration focused on a single smoke test that assures the
* static `index.html` page renders without console errors.
*/
const captureArtifacts = process.env.PLAYWRIGHT_CAPTURE === '1';

module.exports = defineConfig({
testDir: './',
fullyParallel: true,

Choose a reason for hiding this comment

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

P1 Badge Fix Playwright config using ESM syntax in CommonJS module

The Playwright config is written with import … from while the project still defaults to CommonJS (package.json has no "type": "module"). Node will parse playwright.config.js as CommonJS and throw SyntaxError: Cannot use import statement outside a module before any e2e test runs, so npm run test:e2e:index and the pre‑push hook will always fail. Either convert the file (and the specs) to CommonJS require syntax or mark the file/package as ESM (e.g. rename to .mjs or set "type": "module").

Useful? React with 👍 / 👎.

@gemini-code-assist
Copy link

Summary of Changes

Hello @cs-util, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request significantly upgrades the project's development infrastructure by implementing a comprehensive automation and quality framework. The primary goal is to facilitate autonomous AI agent contributions through structured workflows and rigorous quality gates, while also enhancing the overall development experience for human contributors. It introduces new CI/CD processes, standardized issue/PR templates, and integrates advanced code quality tools to ensure a high standard of code health and maintainability.

Highlights

  • Autonomous AI Agent Integration: Introduced GitHub Actions workflows for Claude and Cursor agents, enabling them to autonomously create branches, implement requirements from issues, and open pull requests. This includes a dedicated PR validation workflow for agent-created PRs.
  • Comprehensive CI/CD Pipeline: Established a robust Continuous Integration pipeline that runs core tests, uploads coverage and mutation reports, and enforces quality gates such as linting, duplication, cycle, and dependency checks. This ensures high code quality and stability.
  • Pre-Push Git Hook: Added a pre-push Git hook that blocks code pushes unless the full test suite passes locally, preventing untested or broken code from entering the repository.
  • Detailed Issue and PR Templates: Created detailed issue templates for agent tasks, bug reports, and feature requests. These templates guide both human and AI contributors by specifying objectives, requirements, testing strategies, and acceptance criteria.
  • Enhanced Tooling and Configuration: Integrated various development tools and configurations, including Babel for transpilation, Prettier for code formatting, ESLint for static analysis, Jest for testing, Stryker for mutation testing, jscpd for duplication detection, dependency-cruiser for architectural checks, and madge for cycle detection. VS Code extension recommendations were also added.
  • Refactored Converter and HTML Export Logic: The converter.js and html-export-helper.js files were refactored to improve readability, maintainability, and testability, including better handling of HTML to Markdown table conversions and content sanitization.
  • Extensive Documentation for Quality and Agents: Added new documentation files covering autonomous agent setup, code quality specifications, coverage ignore guidelines, single-page app development specifications, Git pre-commit hooks, Three.js testing, and Playwright UI test best practices.
Ignored Files
  • Ignored by pattern: .github/workflows/** (4)
    • .github/workflows/ci.yml
    • .github/workflows/claude-agent.yml
    • .github/workflows/cursor-agent.yml
    • .github/workflows/qa-agent.yml
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request introduces an impressive and comprehensive framework for automation and quality control, including CI/CD workflows for AI agents, extensive testing configurations, and detailed documentation. The setup is robust and establishes strong quality gates for the project. The refactoring of converter.js and html-export-helper.js improves code structure.

My main feedback concerns the duplication of logic. The refactored helper files in the root directory have been duplicated as CommonJS modules in the src/ directory, likely for testing purposes. This creates a significant maintenance issue. I've recommended consolidating to a single source of truth for these files. Additionally, I've suggested a minor tweak to the ESLint configuration to make the quality gates even stricter.

Overall, this is a fantastic contribution that significantly matures the project's development process.

Comment on lines +1 to +242
// src/converter.js — Lightweight HTML ⇄ Markdown converter (refactored)
// Works in both browser and Node.js environments.
// Dependencies: showdown, turndown, turndown-plugin-gfm

const Showdown = require('showdown');
const TurndownService = require('turndown');
const gfmPlugin = require('turndown-plugin-gfm').gfm;

function factory(Showdown, TurndownService, gfmPlugin) {
if (!Showdown || !TurndownService) {
throw new Error('Showdown and Turndown must be loaded before converter.js');
}

const showdownConverter = new Showdown.Converter({
tables: true,
strikethrough: true,
tasklists: true,
ghCodeBlocks: true,
parseImgDimensions: true,
ghCompatibleHeaderId: true,
requireSpaceBeforeHeadingText: true,
tablesHeaderId: true,
simpleLineBreaks: false,
footnotes: true,
ghMentions: false,
keepReferences: true,
});
showdownConverter.setFlavor('github');

const turndownService = new TurndownService({
headingStyle: 'atx',
bulletListMarker: '-',
codeBlockStyle: 'fenced',
emDelimiter: '*',
strongDelimiter: '**',
linkStyle: 'inlined',
});

if (gfmPlugin) {
turndownService.use(gfmPlugin);
}

turndownService.addRule('smartDel', {
filter: ['del'],
replacement: function (content) {
const htmlPattern = /[<>&]|html|tag|element|attribute/i;
if (htmlPattern.test(content)) {
return '<del>' + content + '</del>';
}
return '~~' + content + '~~';
},
});

// Helper to build headerLine and alignmentRow
function buildHeaderAlignment(headers) {
if (!headers.length) return { headerLine: '', alignmentRow: '' };
const headerLine =
'| ' + headers.map((th) => th.textContent.trim()).join(' | ') + ' |';
let alignmentRow = '|';
for (const header of headers) {
const align =
header.style.textAlign || getComputedStyle(header).textAlign;
let marker;
switch (align) {
case 'left':
marker = ' :--- ';
break;
case 'center':
marker = ' :----: ';
break;
case 'right':
marker = ' ----: ';
break;
default:
marker = ' --- ';
break;
}
alignmentRow += marker + '|';
}
return { headerLine, alignmentRow };
}

// Helper to build body lines
function buildBodyLines(rows, headers) {
const bodyLines = [];
rows.forEach((row) => {
const cells = Array.from(row.querySelectorAll('td'));
const rowLine =
'| ' +
cells
.map((cell, idx) => {
const content = cell.textContent.trim();
const align =
cell.style.textAlign || getComputedStyle(cell).textAlign;
if (align === 'center') {
const totalWidth = headers[idx]
? headers[idx].textContent.trim().length
: content.length;
const padding = Math.max(totalWidth - content.length, 0);
const leftPad = Math.floor(padding / 2);
const rightPad = padding - leftPad;
return ' '.repeat(leftPad) + content + ' '.repeat(rightPad);
} else if (align === 'right') {
const totalWidth = headers[idx]
? headers[idx].textContent.trim().length
: content.length;
const padding = Math.max(totalWidth - content.length, 0);
return ' '.repeat(padding) + content;
}
return content + ' '.repeat(4);
})
.join(' | ') +
' |';
bodyLines.push(rowLine);
});
return bodyLines;
}

// Custom list item rule (kept small)
turndownService.addRule('customListItem', {
filter: 'li',
replacement: function (content, node, options) {
content = content.replace(/\\n/g, '\n');
let isNestedListContent = content.startsWith(' ');
if (!isNestedListContent) content = content.trim();
if (content.includes('\n') && !isNestedListContent) {
content = content.replace(/\n/gm, '\n ');
}

var prefix = '';
var parent = node.parentNode;
if (parent.nodeName === 'OL') {
var start = parent.getAttribute('start');
var index = Array.prototype.indexOf.call(parent.children, node);
prefix = (start ? Number(start) + index : index + 1) + '. ';
} else {
prefix = options.bulletListMarker + ' ';
}

let trailingNewline = '';
if (node.nextSibling) {
trailingNewline = '\n';
} else if (content.includes('\n')) {
trailingNewline = '\n';
}

return prefix + content + trailingNewline;
},
});

turndownService.keep(['kbd']);

turndownService.addRule('tableCell', {
filter: ['th', 'td'],
replacement: function (content, node) {
const trimmedContent = content.trim();
const align = node.style.textAlign || getComputedStyle(node).textAlign;
if (align === 'center') return ' ' + trimmedContent + ' ';
if (align === 'right') return ' ' + trimmedContent + ' ';
return ' ' + trimmedContent + ' ';
},
});

turndownService.addRule('tableWithAlignment', {
filter: 'table',
replacement: function (content, node) {
if (!content.trim()) return '';
const headers = Array.from(node.querySelectorAll('thead th'));
let { headerLine, alignmentRow } = buildHeaderAlignment(headers);
let rows = Array.from(node.querySelectorAll('tbody tr'));
let bodyLines = buildBodyLines(rows, headers);

if (!headerLine && rows.length > 0) {
const firstRowCells = Array.from(rows[0].querySelectorAll('td'));
if (firstRowCells.length > 0) {
headerLine =
'| ' +
firstRowCells.map((cell) => cell.textContent.trim()).join(' | ') +
' |';
alignmentRow =
'| ' + firstRowCells.map(() => '---').join(' | ') + ' |';
bodyLines.shift();
} else {
return '';
}
} else if (!headerLine && rows.length === 0) {
return '';
}

return (
headerLine +
'\n' +
alignmentRow +
(bodyLines.length > 0 ? '\n' + bodyLines.join('\n') : '')
);
},
});

turndownService.addRule('footnoteRef', {
filter: (node) => {
if (node.nodeName === 'A' && node.getAttribute('rel') === 'footnote') {
const href = node.getAttribute('href');
if (href && href.startsWith('#fn-')) return true;
}
return false;
},
replacement: (content, node) => {
const href = node.getAttribute('href');
const footnoteId = href.substring(4);
return `[^${footnoteId}]`;
},
});

turndownService.addRule('footnoteDefinition', {
filter: (node) =>
node.nodeName === 'LI' && node.id && node.id.startsWith('fn-'),
replacement: (content, node) => {
const id = node.id.substring(3);
const clonedNode = node.cloneNode(true);
const backlink = clonedNode.querySelector('a[rev="footnote"]');
if (backlink) backlink.parentNode.removeChild(backlink);
let footnoteText = turndownService.turndown(clonedNode.innerHTML).trim();
return `[^${id}]: ${footnoteText}\n`;
},
});

function markdownToHtml(markdown) {
return showdownConverter.makeHtml(markdown);
}

function htmlToMarkdown(html) {
let markdown = turndownService.turndown(html);
markdown = markdown.replace(/\\\[\^([\w\d-]+)\\\]/g, '[^$1]');
markdown = markdown.replace(/\\\[\^([\w\d-]+)\\\]:/g, '[^$1]:');
markdown = markdown.replace(/\\\[\^([^\]]+)\\\]/g, '[^$1]');
return markdown;
}

return { markdownToHtml, htmlToMarkdown };
}

module.exports = factory(Showdown, TurndownService, gfmPlugin);

Choose a reason for hiding this comment

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

high

This file duplicates the logic from converter.js located in the root directory. Having two files with the same logic creates a significant maintenance burden, as any changes must be manually synchronized between them. This can easily lead to inconsistencies and bugs.

To resolve this, I recommend establishing a single source of truth. Since the project aims for a 'no-build' setup for the browser, the UMD-wrapped file in the root (/converter.js) is necessary. Jest is capable of consuming UMD files, so the tests can be updated to import from the root file.

Please remove this file (src/converter.js) and update the corresponding tests in tests/ to import ../converter.js instead.

Comment on lines +1 to +113
// src/html-export-helper.js — HTML Export Utilities (refactored)
/* global DOMPurify */
function factory() {
function sanitizeContent(content) {
if (typeof DOMPurify !== 'undefined' && DOMPurify.sanitize) {
return DOMPurify.sanitize(content, {
KEEP_CONTENT: true,
ALLOWED_TAGS: [
'p',
'br',
'strong',
'em',
'u',
's',
'h1',
'h2',
'h3',
'h4',
'h5',
'h6',
'ul',
'ol',
'li',
'blockquote',
'a',
'img',
'table',
'thead',
'tbody',
'tr',
'th',
'td',
'code',
'pre',
'span',
'div',
'sub',
'sup',
],
ALLOWED_ATTR: [
'href',
'src',
'alt',
'title',
'class',
'style',
'target',
'rel',
],
});
}

return content
.replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, '')
.replace(/<iframe\b[^<]*(?:(?!<\/iframe>)<[^<]*)*<\/iframe>/gi, '')
.replace(/javascript:/gi, '')
.replace(/on\w+="[^"]*"/gi, '')
.replace(/on\w+='[^']*'/gi, '');
}

function buildStyles(isDark) {
return `
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
line-height: 1.6;
max-width: 800px;
margin: 0 auto;
padding: 20px;
color: ${isDark ? '#e0e0e0' : '#333'};
background-color: ${isDark ? '#1e1e1e' : '#fff'};
}
h1, h2, h3, h4, h5, h6 {
color: ${isDark ? '#fff' : '#000'};
margin: 1.5em 0 0.5em 0;
}
code, pre { font-family: "Monaco", "Consolas", monospace; background-color: ${isDark ? '#374151' : '#f5f5f5'}; }
code { padding: 0.2em 0.4em; border-radius: 3px; }
pre { padding: 1em; border-radius: 5px; overflow-x: auto; }
pre code { background: transparent; padding: 0; }
blockquote { border-left: 4px solid ${isDark ? '#4b5563' : '#ddd'}; margin: 1em 0; padding-left: 1em; color: ${isDark ? '#a0a0a0' : '#666'}; }
table { border-collapse: collapse; width: 100%; margin: 1em 0; }
th, td { border: 1px solid ${isDark ? '#4b5563' : '#ddd'}; padding: 8px 12px; text-align: left; }
th { background-color: ${isDark ? '#374151' : '#f9f9f9'}; font-weight: bold; }
img { max-width: 100%; height: auto; }
a { color: ${isDark ? '#60a5fa' : '#0066cc'}; }
.ql-align-center { text-align: center; }
.ql-align-right { text-align: right; }
.ql-align-justify { text-align: justify; }
@media print { body { color: #000; background: #fff; } }
`;
}

function createCompleteHtmlDocument(content, title, isDark = false) {
const sanitizedContent = sanitizeContent(content);
const styles = buildStyles(isDark);
return `<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>${title}</title>
<style>${styles}</style>
</head>
<body>
${sanitizedContent}
</body>
</html>`;
}

return { createCompleteHtmlDocument };
}

module.exports = factory();

Choose a reason for hiding this comment

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

high

Similar to src/converter.js, this file duplicates the logic from html-export-helper.js in the root directory. This introduces a maintenance risk, as the two files can diverge over time.

To ensure there is a single source of truth, I recommend removing this file and updating the tests to use the UMD module from the root directory (/html-export-helper.js). This will simplify maintenance and prevent potential bugs from unsynchronized changes.

Comment on lines +36 to +37
complexity: ['warn', 10],
'max-depth': ['warn', 4],

Choose a reason for hiding this comment

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

medium

The complexity rules (complexity and max-depth) are currently set to 'warn'. To better enforce code quality and maintainability, it's recommended to set these to 'error'. This will make the linting process fail for overly complex code, turning this quality check into a strict gate rather than a suggestion. This aligns with the project's goal of enforcing high quality standards.

Suggested change
complexity: ['warn', 10],
'max-depth': ['warn', 4],
complexity: ['error', 10],
'max-depth': ['error', 4],

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants