From dd06c893487da34bf64b5735d63a2499b05322cc Mon Sep 17 00:00:00 2001 From: Anthony Lionnet Date: Wed, 1 Jul 2026 15:47:34 +0300 Subject: [PATCH 1/2] feat(cli): optimize agent fix prompt for token efficiency --- .../cli/src/ui/__tests__/agent-prompt.test.ts | 72 ++++++++++++++ apps/cli/src/ui/agent-prompt.ts | 98 +++++++++++++------ 2 files changed, 139 insertions(+), 31 deletions(-) create mode 100644 apps/cli/src/ui/__tests__/agent-prompt.test.ts diff --git a/apps/cli/src/ui/__tests__/agent-prompt.test.ts b/apps/cli/src/ui/__tests__/agent-prompt.test.ts new file mode 100644 index 0000000..6e1cf62 --- /dev/null +++ b/apps/cli/src/ui/__tests__/agent-prompt.test.ts @@ -0,0 +1,72 @@ +import { describe, it, expect } from "vitest" +import type { AuditResult } from "@agentimization/shared" +import { generateClipboardPrompt, generateAgentPrompt } from "../agent-prompt.js" + +const resultWith = (): AuditResult => ({ + url: "https://example.com/", + timestamp: "2026-01-01T00:00:00Z", + overall_score: 56, + grade: "C", + checks: [ + { + id: "topical-authority-signals", + name: "Topical Authority Signals", + category: "geo-signals", + status: "fail", + message: "Weak internal linking: avg 0 internal links/page", + suggestion: "Increase internal linking between related pages. Generative engines use link density to assess topical authority.", + metadata: { avgLinks: 0, pagesWithGoodLinking: 0 }, + }, + ], + summary: { total: 1, passed: 0, warned: 0, failed: 1, skipped: 0 }, + categories: { "geo-signals": { score: 41, checks: 1, passed: 0 } }, + latency_ms: 1, +}) + +const opts = { mode: "remote" as const, target: "https://example.com/" } + +const resultWithSuggestion = (suggestion: string): AuditResult => { + const r = resultWith() + r.checks[0]!.suggestion = suggestion + return r +} + +describe("generateClipboardPrompt (terse, agent-facing)", () => { + it("renders each issue as '- (STATUS): ...' with an indented -> fix", () => { + const out = generateClipboardPrompt(resultWith(), opts) + expect(out).toContain("- topical-authority-signals (FAIL): Weak internal linking") + expect(out).toMatch(/\n {2}-> Increase internal linking/) + }) + + it("strips trailing rationale sentences from the fix", () => { + const out = generateClipboardPrompt( + resultWithSuggestion("Do the thing. Generative engines love it."), + opts, + ) + expect(out).toContain("-> Do the thing.") + expect(out).not.toContain("Generative engines") + }) + + it("rescues a URL out of a dropped rationale sentence", () => { + const out = generateClipboardPrompt( + resultWithSuggestion("Do the thing. AI agents use it, see https://example.com/spec here."), + opts, + ) + expect(out).not.toContain("AI agents use it") + expect(out).toContain("https://example.com/spec") + }) +}) + +describe("generateAgentPrompt (verbose, human-facing report)", () => { + it("keeps markdown decoration (### headings, **Fix:**)", () => { + const out = generateAgentPrompt(resultWith(), opts) + expect(out).toContain("### ") + expect(out).toContain("**Fix:**") + }) + + it("emits a Success line for a check in the table, with interpolated metadata", () => { + const out = generateAgentPrompt(resultWith(), opts) + expect(out).toMatch(/- \*\*Success:\*\* avg >=5 internal links\/page/) + expect(out).toContain("now avg 0/page") + }) +}) diff --git a/apps/cli/src/ui/agent-prompt.ts b/apps/cli/src/ui/agent-prompt.ts index f4aeac6..5b507b1 100644 --- a/apps/cli/src/ui/agent-prompt.ts +++ b/apps/cli/src/ui/agent-prompt.ts @@ -1,35 +1,67 @@ import type { AuditResult, CheckResult } from "@agentimization/shared" import { execSync } from "node:child_process" import { platform } from "node:os" +import { CATEGORY_LABELS } from "./tokens.js" interface PromptOptions { mode: "remote" | "local" target: string } -const CATEGORY_LABELS: Record = { - "content-discoverability": "Content Discoverability", - "markdown-availability": "Markdown Availability", - "content-structure": "Content Structure", - "page-size": "Page Size & Rendering", - "url-stability": "URL Stability", - "authentication": "Authentication & Access", - "geo-signals": "GEO Signals", - "agent-protocols": "Agent Protocols", +const statusMarker = (status: CheckResult["status"]): string => { + switch (status) { + case "pass": return "PASS" + case "warn": return "WARN" + case "fail": return "FAIL" + case "skip": return "SKIP" + case "info": return "INFO" + } } -const statusEmoji = (status: CheckResult["status"]): string => { - switch (status) { - case "pass": return "✅" - case "warn": return "⚠️" - case "fail": return "❌" - case "skip": return "⏭️" - case "info": return "ℹ️" +const RATIONALE_LEAD = /^(generative engines|ai (agents|engines|crawlers|search)|this |these |without (it|this)|used by|some agents|blocked agents|missing content|each redirect|citing sources|shorter descriptions|the more context)\b/i + +const terseSuggestion = (suggestion: string): string => { + const sentences = suggestion.split(/(?<=\.)\s+(?=[A-Z])/) + const kept: string[] = [] + const rescuedUrls: string[] = [] + for (const raw of sentences) { + const s = raw.trim() + if (!s) continue + if (RATIONALE_LEAD.test(s)) { + const url = s.match(/https?:\/\/\S+/)?.[0] + if (url && !suggestion.slice(0, suggestion.indexOf(s)).includes(url)) rescuedUrls.push(url.replace(/[.)]+$/, "")) + continue + } + kept.push(s) } + const base = (kept.join(" ") || suggestion).trim() + return rescuedUrls.length > 0 ? `${base} ${rescuedUrls.join(" ")}` : base +} + +const asciiPunct = (s: string): string => s.replace(/[—–]/g, "-").replace(/·/g, "-").replace(/→/g, "->").replace(/;/g, ",") + +type Concrete = { success: string } +const SUCCESS_TABLE: Record) => Concrete)> = { + "llms-txt-exists": { success: "GET /llms.txt returns 200 with an H1, a blockquote summary, and >=1 ## link section." }, + "sitemap-exists": { success: "GET /sitemap.xml returns 200 valid XML listing all public pages." }, + "markdown-url-support": (m) => ({ success: `appending .md to each page URL returns 200 text/markdown (now ${m.supported ?? 0}/${m.total ?? "?"}).` }), + "structured-data-coverage": { success: "every sampled page has a valid schema.org JSON-LD block." }, + "topical-authority-signals": (m) => ({ success: `avg >=5 internal links/page and >=70% of pages have >=3 (now avg ${m.avgLinks ?? 0}/page).` }), + "content-freshness": { success: ">=80% of pages expose a machine-readable date (Last-Modified, meta, or JSON-LD)." }, + "eeat-signals": { success: "each content page names an author with credentials and links to an about/team page." }, + "canonical-url-consistency": { success: "every page has a self-referencing ." }, + "mcp-server-card": { success: "GET /.well-known/mcp/server-card.json returns valid JSON with name + description + >=1 tool." }, + "section-header-quality": { success: "every page has exactly one H1 and no skipped heading levels." }, +} + +const resolveSuccess = (issue: CheckResult): string | undefined => { + const entry = SUCCESS_TABLE[issue.id] + if (!entry) return undefined + return (typeof entry === "function" ? entry(issue.metadata ?? {}) : entry).success } /** Build the issues-only section (used by both full report and clipboard) */ -const buildIssuesBlock = (result: AuditResult, opts: PromptOptions): string[] => { +const buildIssuesBlock = (result: AuditResult, opts: PromptOptions, terse = false): string[] => { const failures = result.checks.filter((c) => c.status === "fail") const warnings = result.checks.filter((c) => c.status === "warn") const issues = [...failures, ...warnings] @@ -40,8 +72,10 @@ const buildIssuesBlock = (result: AuditResult, opts: PromptOptions): string[] => return lines } - lines.push(`Fix the following GEO issues to make this ${opts.mode === "local" ? "project" : "website"} more discoverable by AI agents:`) - lines.push(``) + if (!terse) { + lines.push(`Fix the following GEO issues to make this ${opts.mode === "local" ? "project" : "website"} more discoverable by AI agents:`) + lines.push(``) + } const byCategory = new Map() for (const issue of issues) { @@ -53,14 +87,19 @@ const buildIssuesBlock = (result: AuditResult, opts: PromptOptions): string[] => for (const [cat, catIssues] of byCategory) { const label = CATEGORY_LABELS[cat] ?? cat const catScore = result.categories[cat]?.score ?? "?" - lines.push(`### ${label} (${catScore}/100)`) + lines.push(terse ? label : `### ${label} (${catScore}/100)`) lines.push(``) for (const issue of catIssues) { - lines.push(`- ${statusEmoji(issue.status)} **${issue.id}**: ${issue.message}`) - if (issue.suggestion) { - lines.push(` - **Fix:** ${issue.suggestion}`) + if (terse) { + lines.push(`- ${issue.id} (${statusMarker(issue.status)}): ${asciiPunct(issue.message)}`) + if (issue.suggestion) lines.push(` -> ${asciiPunct(terseSuggestion(issue.suggestion))}`) + continue } + lines.push(`- ${statusMarker(issue.status)} **${issue.id}**: ${issue.message}`) + if (issue.suggestion) lines.push(` - **Fix:** ${issue.suggestion}`) + const success = resolveSuccess(issue) + if (success) lines.push(` - **Success:** ${success}`) } lines.push(``) @@ -73,17 +112,14 @@ const buildIssuesBlock = (result: AuditResult, opts: PromptOptions): string[] => export const generateClipboardPrompt = (result: AuditResult, opts: PromptOptions): string => { const lines: string[] = [] - lines.push(`# Fix GEO issues — ${opts.target}`) - lines.push(``) - lines.push(`Score: ${result.grade} (${result.overall_score}/100) · ${result.summary.failed} failed, ${result.summary.warned} warnings`) + const subject = opts.mode === "local" ? "project" : "website" + lines.push(`Fix these GEO issues on ${opts.target} so AI agents can discover this ${subject}. Fixes are grouped by area. Do FAIL before WARN.`) lines.push(``) - lines.push(...buildIssuesBlock(result, opts)) + lines.push(...buildIssuesBlock(result, opts, true)) if (opts.mode === "local") { lines.push(`Files are at \`${opts.target}\`. Fix the issues above, then re-run \`agentimization ${opts.target}\` to verify.`) - } else { - lines.push(`Prioritize failures (❌) over warnings (⚠️). Suggest specific code changes.`) } return lines.join("\n") @@ -113,7 +149,7 @@ export const generateAgentPrompt = (result: AuditResult, opts: PromptOptions): s lines.push(`These checks are already good (don't break them while fixing the issues above):`) lines.push(``) for (const pass of passes) { - lines.push(`- ✅ **${pass.id}**: ${pass.message}`) + lines.push(`- PASS **${pass.id}**: ${pass.message}`) } lines.push(``) } @@ -128,7 +164,7 @@ export const generateAgentPrompt = (result: AuditResult, opts: PromptOptions): s } else { lines.push(`This is a remote site audit of ${opts.target}.`) lines.push(`Please suggest the specific code changes needed to fix each issue.`) - lines.push(`Prioritize failures (❌) over warnings (⚠️).`) + lines.push(`Prioritize FAIL over WARN.`) } lines.push(``) From 0f68caf56faddb76050cf4d7fed26c70fe9cda24 Mon Sep 17 00:00:00 2001 From: Anthony Lionnet Date: Wed, 1 Jul 2026 15:49:18 +0300 Subject: [PATCH 2/2] chore: add changeset for agent fix prompt --- .changeset/agent-prompt-token-diet.md | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 .changeset/agent-prompt-token-diet.md diff --git a/.changeset/agent-prompt-token-diet.md b/.changeset/agent-prompt-token-diet.md new file mode 100644 index 0000000..c51d87f --- /dev/null +++ b/.changeset/agent-prompt-token-diet.md @@ -0,0 +1,11 @@ +--- +"agentimization": patch +"@agentimization/core": patch +--- + +optimize the "copy fix prompt" output for AI agents: + +- flatten the clipboard prompt to a plain directive list (drop `###` headings, `**bold**`, `**Fix:**` labels, scores, and the failed/warning counts) +- strip why-this-matters rationale from each fix, keeping the imperative and rescuing any doc URL +- replace status emoji with `PASS`/`WARN`/`FAIL` text markers and flatten non-ascii punctuation (em/en-dash, middot, arrow, semicolon) +- add a per-check `Success:` criterion to the verbose `--md` report so a reader can tell when an issue is fixed