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
11 changes: 11 additions & 0 deletions .changeset/agent-prompt-token-diet.md
Original file line number Diff line number Diff line change
@@ -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
72 changes: 72 additions & 0 deletions apps/cli/src/ui/__tests__/agent-prompt.test.ts
Original file line number Diff line number Diff line change
@@ -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 '- <id> (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")
})
})
98 changes: 67 additions & 31 deletions apps/cli/src/ui/agent-prompt.ts
Original file line number Diff line number Diff line change
@@ -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<string, string> = {
"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<string, Concrete | ((m: Record<string, unknown>) => 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 <link rel=\"canonical\">." },
"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]
Expand All @@ -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<string, CheckResult[]>()
for (const issue of issues) {
Expand All @@ -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(``)
Expand All @@ -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")
Expand Down Expand Up @@ -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(``)
}
Expand All @@ -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(``)
Expand Down
Loading