125 lines
5.6 KiB
JavaScript
125 lines
5.6 KiB
JavaScript
// Five rule sources lifted from netcracker/interview.
|
|
// Toggling a rule injects its body into the ruled prompt.
|
|
const RULES = [
|
|
{
|
|
id: 'agents',
|
|
name: 'AGENTS.md',
|
|
kind: 'Repo-wide instruction',
|
|
path: 'AGENTS.md',
|
|
en: 'Read AGENTS.md before touching this repo. Stack: pnpm + turbo monorepo, Go API, Next.js apps. Gates run from the monorepo root: pnpm lint, typecheck, test.',
|
|
pt: 'Leia AGENTS.md antes de tocar neste repo. Stack: pnpm + turbo monorepo, API em Go, apps Next.js. Gates rodam da raiz: pnpm lint, typecheck, test.'
|
|
},
|
|
{
|
|
id: 'skill',
|
|
name: 'gate-discipline skill',
|
|
kind: 'Skill body',
|
|
path: '.agents/skills/gate-discipline/SKILL.md',
|
|
en: 'Skill `gate-discipline`: every gate runs separately with `$?`. No `| tail`. `TURBO_FORCE=true` if a pass looks too cheap. Generated code is regenerated, never hand-edited.',
|
|
pt: 'Skill `gate-discipline`: cada gate roda separado com `$?`. Nada de `| tail`. `TURBO_FORCE=true` se o passar for bom demais. Código gerado se regenera, nunca se edita.'
|
|
},
|
|
{
|
|
id: 'husky',
|
|
name: 'Husky pre-commit',
|
|
kind: 'Git hook (commit time)',
|
|
path: '.husky/pre-commit',
|
|
en: 'Pre-commit runs `pnpm exec lint-staged`, then `node scripts/check-ui-contract.mjs` (UI ratchet), then commitlint. Counts in the baseline may only go DOWN.',
|
|
pt: 'Pre-commit roda `pnpm exec lint-staged`, depois `node scripts/check-ui-contract.mjs` (ratchet de UI), depois commitlint. Contadores do baseline só podem DIMINUIR.'
|
|
},
|
|
{
|
|
id: 'enforcer',
|
|
name: 'check-ui-contract.mjs',
|
|
kind: 'Custom enforcer (CLI)',
|
|
path: 'scripts/check-ui-contract.mjs',
|
|
en: 'Enforcer scans for raw <button>, silent catches, pages without h1, hardcoded colors, duplicated components. Fails when a count goes UP. Fix = `--accept` to re-baseline lower.',
|
|
pt: 'Enforcer varre <button> cru, catch silencioso, páginas sem h1, cores hardcoded, componentes duplicados. Falha quando contador SOBE. Corrigir = `--accept` para re-baseline menor.'
|
|
},
|
|
{
|
|
id: 'commitlint',
|
|
name: 'commitlint',
|
|
kind: 'Commit-msg linter',
|
|
path: 'commitlint.config.cjs',
|
|
en: 'Commitlint enforces Conventional Commits. Format: `<type>(<scope>): <subject>`. Types: feat, fix, docs, refactor, test, chore, build, ci, perf, style.',
|
|
pt: 'Commitlint enforça Commits Convencionais. Formato: `<tipo>(<escopo>): <assunto>`. Tipos: feat, fix, docs, refactor, test, chore, build, ci, perf, style.'
|
|
}
|
|
];
|
|
|
|
// The task we're asking the agent to perform.
|
|
const TASK = {
|
|
en: { goal: 'Refactor the `getUserById` endpoint to return 404 instead of throwing.', ctx: 'services/api/internal/user/handler.go. No DB schema change.' },
|
|
pt: { goal: 'Refatorar o endpoint `getUserById` para retornar 404 em vez de lançar exceção.', ctx: 'services/api/internal/user/handler.go. Sem mudança de schema.' }
|
|
};
|
|
|
|
const NAIVE = {
|
|
en: `Task: ${TASK.en.goal}\nFile: ${TASK.en.ctx}\nPlease make the change and tell me when done.`,
|
|
pt: `Tarefa: ${TASK.pt.goal}\nArquivo: ${TASK.pt.ctx}\nFaça a mudança e me avise quando terminar.`
|
|
};
|
|
|
|
const state = { enabled: new Set(['agents']), lang: 'en' };
|
|
|
|
const ruleList = document.querySelector('#rule-list');
|
|
const ruleCount = document.querySelector('#rule-count');
|
|
const promptNaive = document.querySelector('#prompt-naive');
|
|
const promptRuled = document.querySelector('#prompt-ruled');
|
|
const copyBtn = document.querySelector('#copy-btn');
|
|
const langButtons = document.querySelectorAll('.lang-switch button');
|
|
|
|
function renderRules() {
|
|
ruleList.innerHTML = RULES.map((rule) => `
|
|
<article class="rule" data-id="${rule.id}">
|
|
<div>
|
|
<h3>${rule.name}</h3>
|
|
<p>${rule.kind} <code>${rule.path}</code></p>
|
|
</div>
|
|
<span class="rule-tag">${rule.id}</span>
|
|
<button class="toggle" type="button" data-rule="${rule.id}" aria-pressed="${state.enabled.has(rule.id)}" aria-label="Toggle ${rule.name}"></button>
|
|
</article>
|
|
`).join('');
|
|
ruleCount.textContent = `${state.enabled.size} / ${RULES.length} active`;
|
|
}
|
|
|
|
function renderPrompts() {
|
|
const lang = state.lang;
|
|
promptNaive.textContent = NAIVE[lang];
|
|
|
|
const active = RULES.filter((r) => state.enabled.has(r.id));
|
|
const blocks = active.map((r) => `# ${r.name} (${r.path})\n${r[lang]}`).join('\n\n');
|
|
const head = `Task: ${TASK[lang].goal}\nFile: ${TASK[lang].ctx}`;
|
|
const tail = lang === 'en'
|
|
? '\n\nRun each gate separately and print $? before claiming done.'
|
|
: '\n\nRode cada gate separado e imprima $? antes de dizer que terminou.';
|
|
promptRuled.textContent = blocks ? `${head}\n\n${blocks}${tail}` : head + tail;
|
|
}
|
|
|
|
function bind() {
|
|
ruleList.addEventListener('click', (e) => {
|
|
const btn = e.target.closest('.toggle');
|
|
if (!btn) return;
|
|
const id = btn.dataset.rule;
|
|
if (state.enabled.has(id)) state.enabled.delete(id); else state.enabled.add(id);
|
|
btn.setAttribute('aria-pressed', String(state.enabled.has(id)));
|
|
ruleCount.textContent = `${state.enabled.size} / ${RULES.length} active`;
|
|
renderPrompts();
|
|
});
|
|
|
|
langButtons.forEach((btn) => btn.addEventListener('click', () => {
|
|
state.lang = btn.dataset.lang;
|
|
langButtons.forEach((b) => b.setAttribute('aria-pressed', String(b === btn)));
|
|
renderPrompts();
|
|
}));
|
|
|
|
copyBtn.addEventListener('click', async () => {
|
|
try {
|
|
await navigator.clipboard.writeText(promptRuled.textContent);
|
|
copyBtn.dataset.copied = 'true';
|
|
copyBtn.textContent = 'Copied';
|
|
setTimeout(() => { copyBtn.dataset.copied = 'false'; copyBtn.textContent = 'Copy ruled prompt'; }, 1400);
|
|
} catch {
|
|
copyBtn.textContent = 'Copy failed — select and copy manually';
|
|
}
|
|
});
|
|
}
|
|
|
|
renderRules();
|
|
renderPrompts();
|
|
bind();
|