/** * checks/comments.ts — comments hygiene check (first end-to-end reference). * * This is the canonical `CheckDefinition` that future checks (08+) copy. It * reuses the generic `scanner`/`fixer` agents shipped in `agents/*.md`; the * check-specific rubric is embedded in the task text handed to the sub-agent * (see {@link buildCommentsScanTask} / {@link buildCommentsFixTask}) so no * per-check agent `.md` file is required. * * Rubric (the user's spec — short + high value): * - comments that restate the code they sit on ("what" comments) → REMOVE * - verbose narration / long-winded explanations → TIGHTEN (shorten) * - "why" comments that explain intent, rationale, or gotchas → KEEP * - code self-explanatory with no comment → no comment needed (don't add one) * * Artifacts (under `/pygienium/checks/comments/`): * - `findings.md` — per-file line refs for each smell * - `changes.md` — summary of edits + human-review items * * @module pygienium/checks/comments */ import { registerCheck, type CheckScope } from "./registry.js"; /** Phase-strip phase this check belongs to. */ export const COMMENTS_PHASE_ID = "C1"; /** * Directory where this check writes its `findings.md` and `changes.md` * artifacts: `/pygienium/checks/comments/`. Based on `scope.cwd` (the * project root, always a directory) so the path is valid whether the scan * target is a single file or a directory. Matches the spec's * `pygienium/checks/comments/findings.md` relative-path notation. */ export function commentsArtifactDir(scope: CheckScope): string { const base = scope.cwd.replace(/\/+$/, ""); return `${base}/pygienium/checks/comments`; } /** Absolute path to the findings artifact for this check. */ export function findingsPath(scope: CheckScope): string { return `${commentsArtifactDir(scope)}/findings.md`; } /** Absolute path to the changes artifact for this check. */ export function changesPath(scope: CheckScope): string { return `${commentsArtifactDir(scope)}/changes.md`; } /** * Shared rubric block, injected into both scan and fix task text so the analysis * and remediation sub-agents apply identical judgement. */ const RUBRIC = `# Comments hygiene rubric Short + high value is the goal. Evaluate every comment in the target: - **RESTATE → REMOVE.** A comment that paraphrases the line(s) it sits on adds no information. Examples: \`// increment i\` over \`i++\`, \`// return the result\` over \`return result\`. Delete it. - **VERBOSE → TIGHTEN.** A comment that is high-value but needlessly long. Rewrite it to one tight sentence preserving the key insight. Do not delete. - **"WHY" → KEEP.** A comment explaining intent, rationale, a non-obvious decision, a workaround, a gotcha, or a constraint the code cannot express. Leave it untouched (tighten only if it is also verbose). - **NO COMMENT NEEDED.** When the code is self-explanatory, do not add a comment. - Keep inline section headers/dividers that aid navigation only if they mark a real boundary; remove pure decoration.`; /** * Build the analysis sub-agent task. Instructs the agent to read candidate * source files, identify comment smells per the rubric, and write per-file line * references to `pygienium/comments/findings.md`. */ export function buildCommentsScanTask(_cwd: string, scope: CheckScope): string { const outDir = commentsArtifactDir(scope); const findingsFile = findingsPath(scope); return `# Task: comments hygiene scan You are running the **comments** hygiene check. ## Target - Scan target: \`${scope.target}\` ## What to do 1. Read the recon snapshot at \`${scope.cwd}/.pygienium/recon.json\` if it exists; otherwise enumerate source files directly under the target. 2. For each source file, read it and locate every comment (inline \`//\`, block \`/* */\`, doc \`/** */\`, \`#\` for scripting languages, etc.). 3. Apply the rubric below to each comment and classify it: RESTATE, VERBOSE, WHY, or OK. 4. Write a findings report to \`${findingsFile}\` with per-file line refs. 5. Return the findings report text as your final message (same content as the file). The host captures it as the analysis-phase findings. ${RUBRIC} ## findings.md format \`\`\`markdown # comments — findings comment smell(s) across file(s). ## - L: - L: KEEP (why) — # listed for transparency \`\`\` If no smells are found, write \`# comments — findings\n\n0 comment smell(s).\` and return that text. Always create findings.md so the run has an artifact. Write the report under \`${outDir}\` (create directories as needed). `; } /** * Build the fix sub-agent task from the scan findings. Instructs the agent to * apply safe removals/tightenings, leave "why" comments, and write a summary * of edits plus anything needing human review to `pygienium/comments/changes.md`. */ export function buildCommentsFixTask( _cwd: string, scope: CheckScope, findings: string, ): string { const outDir = commentsArtifactDir(scope); const changesFile = changesPath(scope); return `# Task: comments hygiene fix You are running the **comments** hygiene fix phase. ## Target - Fix target: \`${scope.target}\` ## Input: scan findings ${findings.trim().length > 0 ? findings : "(no findings text provided)"} ## What to do 1. For each RESTATE finding: remove the comment entirely. 2. For each VERBOSE finding: replace the comment with a tightened one-sentence version that keeps the key insight. 3. For every WHY comment: leave it untouched (tighten only if it is also verbose, preserving the rationale). 4. Do not change any code logic, formatting, or ordering — only comments. 5. Write a summary to \`${changesFile}\` and return it as your final message. ${RUBRIC} ## changes.md format \`\`\`markdown # comments — changes edit(s) applied; deferred for human review. ## Applied - : comment (auto) ## Needs human review - : (manual) \`\`\` If nothing needed changing, write \`# comments — changes\n\n0 edit(s) applied.\` and return that text. Always create changes.md so the run has an artifact. Write it under \`${outDir}\`. `; } /** * Precondition gate. Returns an error string when the comments check cannot * proceed (target path missing or not a real file/directory), else * `undefined`. Idempotent — passes identically before analysis and at verify. */ async function commentsGate(cwd: string): Promise { const { stat } = await import("node:fs/promises"); const { resolve } = await import("node:path"); const target = resolve(cwd); try { const s = await stat(target); if (s.isDirectory() || s.isFile()) return undefined; return `target is not a file or directory: ${target}`; } catch { return `target path does not exist: ${target}`; } } /** * Verify hook: confirms the check actually produced its artifacts. After the * scan phase `findings.md` must exist; after the fix phase `changes.md` must * exist too (the scan-phase-only run skips `changes.md` by design). Returns an * error string to fail verify, or `undefined` to pass. Replaces the historical * no-op verify (which only re-ran the existence gate) so the verify phase now * genuinely asserts the run produced its report. */ async function commentsVerify(scope: CheckScope): Promise { const { stat } = await import("node:fs/promises"); const f = findingsPath(scope); try { await stat(f); } catch { return `comments verify: expected findings.md at ${f} after scan, none found.`; } if (scope.fix) { const c = changesPath(scope); try { await stat(c); } catch { return `comments verify: expected changes.md at ${c} after --fix, none found.`; } } return undefined; } /** The comments hygiene check definition. */ export const commentsCheck = { name: "comments", label: "Comments", description: 'Remove low-value/restating comments, tighten verbose ones, keep "why" comments.', agentName: "scanner", fixAgentName: "fixer", phaseId: COMMENTS_PHASE_ID, buildScanTask: buildCommentsScanTask, buildFixTask: buildCommentsFixTask, gate: commentsGate, verify: commentsVerify, } as const; // Self-register on import so index.ts auto-discovery picks it up. registerCheck(commentsCheck);