diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json index 5f9cb2aa..53584715 100644 --- a/.claude-plugin/plugin.json +++ b/.claude-plugin/plugin.json @@ -2,7 +2,7 @@ "name": "seo", "displayName": "seo", "description": "Local-first SEO and AI-search diagnostics. Bundles the seo MCP server plus one SEO skill that gives an agent 50+ audit and report tools without filling its context window.", - "version": "0.2.37", + "version": "0.2.38", "author": { "name": "Ian Nuttall" }, diff --git a/package.json b/package.json index b1ebb743..c1108428 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "seo", - "version": "0.2.37", + "version": "0.2.38", "description": "The SEO command for AI agents. Audit sites and research search opportunities with local, evidence-backed reports.", "type": "module", "license": "Apache-2.0", diff --git a/packages/cli/src/commands/workflows/diagnose-property.test.ts b/packages/cli/src/commands/workflows/diagnose-property.test.ts index 295d7cdc..d44c4529 100644 --- a/packages/cli/src/commands/workflows/diagnose-property.test.ts +++ b/packages/cli/src/commands/workflows/diagnose-property.test.ts @@ -1,6 +1,10 @@ import assert from 'node:assert/strict' import { test } from 'node:test' -import { reportFollowups } from './diagnose-property.js' +import { agentActionsView } from '@seo/core' +import { + reportActionsWithTechnicalEvidence, + reportFollowups, +} from './diagnose-property.js' function reportFixture(): Parameters[0] { return { @@ -87,3 +91,55 @@ test('technical-only report asks for a crawl before a page follow-up', () => { ], ) }) + +test('report actions include technical crawl fixes and reviews', () => { + const searchActions = [{ id: 'search:quick-win' }] + const technicalActions = [ + { + id: 'crawl:title_duplicate', + kind: 'fix', + title: 'Duplicate titles', + action: 'Write a distinct title for each affected page.', + }, + { + id: 'crawl:noindex', + kind: 'review', + title: 'Noindex pages', + action: 'Confirm that each noindex directive is intentional.', + }, + ] + + assert.deepEqual( + reportActionsWithTechnicalEvidence(searchActions, technicalActions), + [...searchActions, ...technicalActions], + ) + assert.deepEqual( + reportActionsWithTechnicalEvidence([], technicalActions), + technicalActions, + ) + + const view = agentActionsView( + { actions: reportActionsWithTechnicalEvidence([], technicalActions) }, + { preferRootActions: true }, + ) + const findings = view.findings as { + counts: { + total: number + returned: number + fixes: number + reviews: number + open: number + } + completion: { state: string } + sourcePaths: string[] + } + assert.deepEqual(findings.counts, { + total: 2, + returned: 2, + fixes: 1, + reviews: 1, + open: 2, + }) + assert.equal(findings.completion.state, 'pending') + assert.deepEqual(findings.sourcePaths, ['actions']) +}) diff --git a/packages/cli/src/commands/workflows/diagnose-property.ts b/packages/cli/src/commands/workflows/diagnose-property.ts index 5b2ca77f..becd55b6 100644 --- a/packages/cli/src/commands/workflows/diagnose-property.ts +++ b/packages/cli/src/commands/workflows/diagnose-property.ts @@ -133,6 +133,15 @@ function compactTechnicalSection(section: ReturnType) { } } +export function reportActionsWithTechnicalEvidence( + reportActions: readonly T[], + technicalActions?: readonly U[], +): Array { + return technicalActions + ? [...reportActions, ...technicalActions] + : [...reportActions] +} + function printTechnicalSection( section: ReturnType, options: { providerFree?: boolean } = {}, @@ -853,7 +862,7 @@ function workflowCommandMeta(input: { overlap: topicOverlap, }) : undefined - const urlModeActions = technicalBaseline?.report + const technicalActions = technicalBaseline?.report ? technicalCrawlActions({ crawlReportId: technicalBaseline.report.id, topFixes: completeTopFixes(technicalBaseline.report), @@ -897,6 +906,10 @@ function workflowCommandMeta(input: { input.printFollowups && !full ? compactMainReportJson(report, input.workflowName) : outputReport + const completeActions = reportActionsWithTechnicalEvidence( + jsonReport.actions, + technicalActions, + ) const reportWithTechnicalEvidence = !useSearchData && technicalCrawl ? { @@ -911,10 +924,7 @@ function workflowCommandMeta(input: { ...exportWorkflowSteps(exportSummaryInput), ...jsonReport.steps, ], - // A provider-free report must surface crawl findings as its - // action queue; an empty list here previously read as "the - // tool found nothing" while the findings sat below. - ...(urlModeActions ? { actions: urlModeActions } : {}), + actions: completeActions, // The compact URL-mode report replaces the empty narrative // shell (every section unavailable) with an explicit skip // marker. --full keeps the whole narrative object. @@ -930,7 +940,7 @@ function workflowCommandMeta(input: { }, }), } - : jsonReport + : { ...jsonReport, actions: completeActions } const completeReport = technicalCrawl || followups ? { diff --git a/packages/cli/src/commands/workflows/url-report-evidence.ts b/packages/cli/src/commands/workflows/url-report-evidence.ts index 96af990f..76e3b5ea 100644 --- a/packages/cli/src/commands/workflows/url-report-evidence.ts +++ b/packages/cli/src/commands/workflows/url-report-evidence.ts @@ -529,10 +529,10 @@ function byPriority(left: PrefixedFix, right: PrefixedFix): number { } /** - * Turn the complete crawl issue inventory into workflow actions so a - * provider-free report never returns an empty action list while the crawl - * holds findings. Fixes carry high confidence because the state was - * observed; review observations stay medium because intent is unconfirmed. + * Turn the complete crawl issue inventory into workflow actions so the main + * report does not return an empty or incomplete action list while the crawl + * holds findings. Fixes carry high confidence because the state was observed; + * review observations stay medium because intent is unconfirmed. */ export function technicalCrawlActions(input: { topFixes: TopFix[]