@nebulacomponents/citable 1.15.1 → 1.17.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +50 -0
- package/README.md +18 -5
- package/dist/universal/.agents/skills/citable/SKILL.md +51 -1
- package/dist/universal/.agents/skills/citable/VERSION +1 -1
- package/dist/universal/.agents/skills/citable/commands/README.md +3 -1
- package/dist/universal/.agents/skills/citable/commands/governance.md +12 -0
- package/dist/universal/.agents/skills/citable/commands/measurement.md +20 -0
- package/dist/universal/.agents/skills/citable/commands/reporting.md +16 -0
- package/dist/universal/.agents/skills/citable/manifest.json +17 -12
- package/dist/universal/.agents/skills/citable/schemas/cli-output-envelope.schema.json +30 -0
- package/dist/universal/.agents/skills/citable/schemas/experiment.schema.json +1 -1
- package/dist/universal/.agents/skills/citable/schemas/finding.schema.json +69 -0
- package/dist/universal/.agents/skills/citable/schemas/fleet.schema.json +93 -0
- package/dist/universal/.agents/skills/citable/schemas/funnel.schema.json +116 -0
- package/dist/universal/.agents/skills/citable/schemas/mcp-transport-envelope.schema.json +244 -0
- package/dist/universal/.agents/skills/citable/schemas/observation.schema.json +1 -1
- package/dist/universal/.agents/skills/citable/schemas/remediation-verification.schema.json +98 -0
- package/dist/universal/.claude/agents/citable/manifest.json +2 -2
- package/dist/universal/.claude/skills/citable/SKILL.md +51 -1
- package/dist/universal/.claude/skills/citable/VERSION +1 -1
- package/dist/universal/.claude/skills/citable/commands/README.md +3 -1
- package/dist/universal/.claude/skills/citable/commands/governance.md +12 -0
- package/dist/universal/.claude/skills/citable/commands/measurement.md +20 -0
- package/dist/universal/.claude/skills/citable/commands/reporting.md +16 -0
- package/dist/universal/.claude/skills/citable/manifest.json +17 -12
- package/dist/universal/.claude/skills/citable/schemas/cli-output-envelope.schema.json +30 -0
- package/dist/universal/.claude/skills/citable/schemas/experiment.schema.json +1 -1
- package/dist/universal/.claude/skills/citable/schemas/finding.schema.json +69 -0
- package/dist/universal/.claude/skills/citable/schemas/fleet.schema.json +93 -0
- package/dist/universal/.claude/skills/citable/schemas/funnel.schema.json +116 -0
- package/dist/universal/.claude/skills/citable/schemas/mcp-transport-envelope.schema.json +244 -0
- package/dist/universal/.claude/skills/citable/schemas/observation.schema.json +1 -1
- package/dist/universal/.claude/skills/citable/schemas/remediation-verification.schema.json +98 -0
- package/dist/universal/.cursor/skills/citable/SKILL.md +51 -1
- package/dist/universal/.cursor/skills/citable/VERSION +1 -1
- package/dist/universal/.cursor/skills/citable/commands/README.md +3 -1
- package/dist/universal/.cursor/skills/citable/commands/governance.md +12 -0
- package/dist/universal/.cursor/skills/citable/commands/measurement.md +20 -0
- package/dist/universal/.cursor/skills/citable/commands/reporting.md +16 -0
- package/dist/universal/.cursor/skills/citable/manifest.json +17 -12
- package/dist/universal/.cursor/skills/citable/schemas/cli-output-envelope.schema.json +30 -0
- package/dist/universal/.cursor/skills/citable/schemas/experiment.schema.json +1 -1
- package/dist/universal/.cursor/skills/citable/schemas/finding.schema.json +69 -0
- package/dist/universal/.cursor/skills/citable/schemas/fleet.schema.json +93 -0
- package/dist/universal/.cursor/skills/citable/schemas/funnel.schema.json +116 -0
- package/dist/universal/.cursor/skills/citable/schemas/mcp-transport-envelope.schema.json +244 -0
- package/dist/universal/.cursor/skills/citable/schemas/observation.schema.json +1 -1
- package/dist/universal/.cursor/skills/citable/schemas/remediation-verification.schema.json +98 -0
- package/dist/universal/.gemini/skills/citable/SKILL.md +51 -1
- package/dist/universal/.gemini/skills/citable/VERSION +1 -1
- package/dist/universal/.gemini/skills/citable/commands/README.md +3 -1
- package/dist/universal/.gemini/skills/citable/commands/governance.md +12 -0
- package/dist/universal/.gemini/skills/citable/commands/measurement.md +20 -0
- package/dist/universal/.gemini/skills/citable/commands/reporting.md +16 -0
- package/dist/universal/.gemini/skills/citable/manifest.json +17 -12
- package/dist/universal/.gemini/skills/citable/schemas/cli-output-envelope.schema.json +30 -0
- package/dist/universal/.gemini/skills/citable/schemas/experiment.schema.json +1 -1
- package/dist/universal/.gemini/skills/citable/schemas/finding.schema.json +69 -0
- package/dist/universal/.gemini/skills/citable/schemas/fleet.schema.json +93 -0
- package/dist/universal/.gemini/skills/citable/schemas/funnel.schema.json +116 -0
- package/dist/universal/.gemini/skills/citable/schemas/mcp-transport-envelope.schema.json +244 -0
- package/dist/universal/.gemini/skills/citable/schemas/observation.schema.json +1 -1
- package/dist/universal/.gemini/skills/citable/schemas/remediation-verification.schema.json +98 -0
- package/dist/universal/.github/skills/citable/SKILL.md +51 -1
- package/dist/universal/.github/skills/citable/VERSION +1 -1
- package/dist/universal/.github/skills/citable/commands/README.md +3 -1
- package/dist/universal/.github/skills/citable/commands/governance.md +12 -0
- package/dist/universal/.github/skills/citable/commands/measurement.md +20 -0
- package/dist/universal/.github/skills/citable/commands/reporting.md +16 -0
- package/dist/universal/.github/skills/citable/manifest.json +17 -12
- package/dist/universal/.github/skills/citable/schemas/cli-output-envelope.schema.json +30 -0
- package/dist/universal/.github/skills/citable/schemas/experiment.schema.json +1 -1
- package/dist/universal/.github/skills/citable/schemas/finding.schema.json +69 -0
- package/dist/universal/.github/skills/citable/schemas/fleet.schema.json +93 -0
- package/dist/universal/.github/skills/citable/schemas/funnel.schema.json +116 -0
- package/dist/universal/.github/skills/citable/schemas/mcp-transport-envelope.schema.json +244 -0
- package/dist/universal/.github/skills/citable/schemas/observation.schema.json +1 -1
- package/dist/universal/.github/skills/citable/schemas/remediation-verification.schema.json +98 -0
- package/dist/universal/.kiro/skills/citable/SKILL.md +51 -1
- package/dist/universal/.kiro/skills/citable/VERSION +1 -1
- package/dist/universal/.kiro/skills/citable/commands/README.md +3 -1
- package/dist/universal/.kiro/skills/citable/commands/governance.md +12 -0
- package/dist/universal/.kiro/skills/citable/commands/measurement.md +20 -0
- package/dist/universal/.kiro/skills/citable/commands/reporting.md +16 -0
- package/dist/universal/.kiro/skills/citable/manifest.json +17 -12
- package/dist/universal/.kiro/skills/citable/schemas/cli-output-envelope.schema.json +30 -0
- package/dist/universal/.kiro/skills/citable/schemas/experiment.schema.json +1 -1
- package/dist/universal/.kiro/skills/citable/schemas/finding.schema.json +69 -0
- package/dist/universal/.kiro/skills/citable/schemas/fleet.schema.json +93 -0
- package/dist/universal/.kiro/skills/citable/schemas/funnel.schema.json +116 -0
- package/dist/universal/.kiro/skills/citable/schemas/mcp-transport-envelope.schema.json +244 -0
- package/dist/universal/.kiro/skills/citable/schemas/observation.schema.json +1 -1
- package/dist/universal/.kiro/skills/citable/schemas/remediation-verification.schema.json +98 -0
- package/dist/universal/.opencode/skills/citable/SKILL.md +51 -1
- package/dist/universal/.opencode/skills/citable/VERSION +1 -1
- package/dist/universal/.opencode/skills/citable/commands/README.md +3 -1
- package/dist/universal/.opencode/skills/citable/commands/governance.md +12 -0
- package/dist/universal/.opencode/skills/citable/commands/measurement.md +20 -0
- package/dist/universal/.opencode/skills/citable/commands/reporting.md +16 -0
- package/dist/universal/.opencode/skills/citable/manifest.json +17 -12
- package/dist/universal/.opencode/skills/citable/schemas/cli-output-envelope.schema.json +30 -0
- package/dist/universal/.opencode/skills/citable/schemas/experiment.schema.json +1 -1
- package/dist/universal/.opencode/skills/citable/schemas/finding.schema.json +69 -0
- package/dist/universal/.opencode/skills/citable/schemas/fleet.schema.json +93 -0
- package/dist/universal/.opencode/skills/citable/schemas/funnel.schema.json +116 -0
- package/dist/universal/.opencode/skills/citable/schemas/mcp-transport-envelope.schema.json +244 -0
- package/dist/universal/.opencode/skills/citable/schemas/observation.schema.json +1 -1
- package/dist/universal/.opencode/skills/citable/schemas/remediation-verification.schema.json +98 -0
- package/dist/universal/.pi/agent/skills/citable/SKILL.md +51 -1
- package/dist/universal/.pi/agent/skills/citable/VERSION +1 -1
- package/dist/universal/.pi/agent/skills/citable/commands/README.md +3 -1
- package/dist/universal/.pi/agent/skills/citable/commands/governance.md +12 -0
- package/dist/universal/.pi/agent/skills/citable/commands/measurement.md +20 -0
- package/dist/universal/.pi/agent/skills/citable/commands/reporting.md +16 -0
- package/dist/universal/.pi/agent/skills/citable/manifest.json +17 -12
- package/dist/universal/.pi/agent/skills/citable/schemas/cli-output-envelope.schema.json +30 -0
- package/dist/universal/.pi/agent/skills/citable/schemas/experiment.schema.json +1 -1
- package/dist/universal/.pi/agent/skills/citable/schemas/finding.schema.json +69 -0
- package/dist/universal/.pi/agent/skills/citable/schemas/fleet.schema.json +93 -0
- package/dist/universal/.pi/agent/skills/citable/schemas/funnel.schema.json +116 -0
- package/dist/universal/.pi/agent/skills/citable/schemas/mcp-transport-envelope.schema.json +244 -0
- package/dist/universal/.pi/agent/skills/citable/schemas/observation.schema.json +1 -1
- package/dist/universal/.pi/agent/skills/citable/schemas/remediation-verification.schema.json +98 -0
- package/dist/universal/.qoder/skills/citable/SKILL.md +51 -1
- package/dist/universal/.qoder/skills/citable/VERSION +1 -1
- package/dist/universal/.qoder/skills/citable/commands/README.md +3 -1
- package/dist/universal/.qoder/skills/citable/commands/governance.md +12 -0
- package/dist/universal/.qoder/skills/citable/commands/measurement.md +20 -0
- package/dist/universal/.qoder/skills/citable/commands/reporting.md +16 -0
- package/dist/universal/.qoder/skills/citable/manifest.json +17 -12
- package/dist/universal/.qoder/skills/citable/schemas/cli-output-envelope.schema.json +30 -0
- package/dist/universal/.qoder/skills/citable/schemas/experiment.schema.json +1 -1
- package/dist/universal/.qoder/skills/citable/schemas/finding.schema.json +69 -0
- package/dist/universal/.qoder/skills/citable/schemas/fleet.schema.json +93 -0
- package/dist/universal/.qoder/skills/citable/schemas/funnel.schema.json +116 -0
- package/dist/universal/.qoder/skills/citable/schemas/mcp-transport-envelope.schema.json +244 -0
- package/dist/universal/.qoder/skills/citable/schemas/observation.schema.json +1 -1
- package/dist/universal/.qoder/skills/citable/schemas/remediation-verification.schema.json +98 -0
- package/dist/universal/.rovodev/skills/citable/SKILL.md +51 -1
- package/dist/universal/.rovodev/skills/citable/VERSION +1 -1
- package/dist/universal/.rovodev/skills/citable/commands/README.md +3 -1
- package/dist/universal/.rovodev/skills/citable/commands/governance.md +12 -0
- package/dist/universal/.rovodev/skills/citable/commands/measurement.md +20 -0
- package/dist/universal/.rovodev/skills/citable/commands/reporting.md +16 -0
- package/dist/universal/.rovodev/skills/citable/manifest.json +17 -12
- package/dist/universal/.rovodev/skills/citable/schemas/cli-output-envelope.schema.json +30 -0
- package/dist/universal/.rovodev/skills/citable/schemas/experiment.schema.json +1 -1
- package/dist/universal/.rovodev/skills/citable/schemas/finding.schema.json +69 -0
- package/dist/universal/.rovodev/skills/citable/schemas/fleet.schema.json +93 -0
- package/dist/universal/.rovodev/skills/citable/schemas/funnel.schema.json +116 -0
- package/dist/universal/.rovodev/skills/citable/schemas/mcp-transport-envelope.schema.json +244 -0
- package/dist/universal/.rovodev/skills/citable/schemas/observation.schema.json +1 -1
- package/dist/universal/.rovodev/skills/citable/schemas/remediation-verification.schema.json +98 -0
- package/dist/universal/.trae/skills/citable/SKILL.md +51 -1
- package/dist/universal/.trae/skills/citable/VERSION +1 -1
- package/dist/universal/.trae/skills/citable/commands/README.md +3 -1
- package/dist/universal/.trae/skills/citable/commands/governance.md +12 -0
- package/dist/universal/.trae/skills/citable/commands/measurement.md +20 -0
- package/dist/universal/.trae/skills/citable/commands/reporting.md +16 -0
- package/dist/universal/.trae/skills/citable/manifest.json +17 -12
- package/dist/universal/.trae/skills/citable/schemas/cli-output-envelope.schema.json +30 -0
- package/dist/universal/.trae/skills/citable/schemas/experiment.schema.json +1 -1
- package/dist/universal/.trae/skills/citable/schemas/finding.schema.json +69 -0
- package/dist/universal/.trae/skills/citable/schemas/fleet.schema.json +93 -0
- package/dist/universal/.trae/skills/citable/schemas/funnel.schema.json +116 -0
- package/dist/universal/.trae/skills/citable/schemas/mcp-transport-envelope.schema.json +244 -0
- package/dist/universal/.trae/skills/citable/schemas/observation.schema.json +1 -1
- package/dist/universal/.trae/skills/citable/schemas/remediation-verification.schema.json +98 -0
- package/dist/universal/.trae-cn/skills/citable/SKILL.md +51 -1
- package/dist/universal/.trae-cn/skills/citable/VERSION +1 -1
- package/dist/universal/.trae-cn/skills/citable/commands/README.md +3 -1
- package/dist/universal/.trae-cn/skills/citable/commands/governance.md +12 -0
- package/dist/universal/.trae-cn/skills/citable/commands/measurement.md +20 -0
- package/dist/universal/.trae-cn/skills/citable/commands/reporting.md +16 -0
- package/dist/universal/.trae-cn/skills/citable/manifest.json +17 -12
- package/dist/universal/.trae-cn/skills/citable/schemas/cli-output-envelope.schema.json +30 -0
- package/dist/universal/.trae-cn/skills/citable/schemas/experiment.schema.json +1 -1
- package/dist/universal/.trae-cn/skills/citable/schemas/finding.schema.json +69 -0
- package/dist/universal/.trae-cn/skills/citable/schemas/fleet.schema.json +93 -0
- package/dist/universal/.trae-cn/skills/citable/schemas/funnel.schema.json +116 -0
- package/dist/universal/.trae-cn/skills/citable/schemas/mcp-transport-envelope.schema.json +244 -0
- package/dist/universal/.trae-cn/skills/citable/schemas/observation.schema.json +1 -1
- package/dist/universal/.trae-cn/skills/citable/schemas/remediation-verification.schema.json +98 -0
- package/dist/universal/manifest.json +208 -148
- package/package.json +1 -1
- package/schemas/cli-output-envelope.schema.json +30 -0
- package/schemas/experiment.schema.json +1 -1
- package/schemas/finding.schema.json +69 -0
- package/schemas/fleet.schema.json +93 -0
- package/schemas/funnel.schema.json +116 -0
- package/schemas/mcp-transport-envelope.schema.json +244 -0
- package/schemas/observation.schema.json +1 -1
- package/schemas/remediation-verification.schema.json +98 -0
- package/skill/SKILL.md +51 -1
- package/skill/commands/README.md +3 -1
- package/skill/commands/governance.md +12 -0
- package/skill/commands/measurement.md +20 -0
- package/skill/commands/reporting.md +16 -0
- package/src/analysis/saliency.js +119 -0
- package/src/cli/index.js +651 -13
- package/src/commands/attributeImpact.js +54 -0
- package/src/commands/audit.js +2 -2
- package/src/commands/ciWorkflow.js +63 -0
- package/src/commands/compatibility.js +205 -0
- package/src/commands/connect.js +3 -0
- package/src/commands/context.js +2 -1
- package/src/commands/edgeRules.js +196 -0
- package/src/commands/edgeSecurity.js +192 -0
- package/src/commands/exceptions.js +241 -0
- package/src/commands/experimentGuardrails.js +213 -0
- package/src/commands/fleet.js +104 -0
- package/src/commands/generateLlmsTxt.js +111 -0
- package/src/commands/goldenCorpus.js +155 -0
- package/src/commands/implementationKit.js +202 -0
- package/src/commands/inspectAeo.js +74 -0
- package/src/commands/inspectCro.js +142 -0
- package/src/commands/inspectGeo.js +52 -0
- package/src/commands/inspectSerp.js +317 -0
- package/src/commands/lintComponents.js +132 -0
- package/src/commands/monitor.js +138 -0
- package/src/commands/observe.js +6 -1
- package/src/commands/planExperiment.js +120 -0
- package/src/commands/previewCro.js +241 -0
- package/src/commands/probe.js +70 -0
- package/src/commands/remediate.js +461 -0
- package/src/commands/reportConsensus.js +48 -0
- package/src/commands/testFunnel.js +107 -0
- package/src/commands/verifyRemediation.js +204 -0
- package/src/commands/visualRegression.js +165 -0
- package/src/components/index.js +556 -0
- package/src/connectors/index.js +6 -1
- package/src/connectors/indexnow.js +431 -0
- package/src/connectors/mcp/allowlist.js +107 -0
- package/src/connectors/mcp/client.js +239 -0
- package/src/connectors/mcp/diagnostics.js +214 -0
- package/src/connectors/mcp/gscServer.js +155 -0
- package/src/connectors/mcp/http.js +130 -0
- package/src/connectors/mcp/index.js +7 -0
- package/src/connectors/mcp/pilot.js +138 -0
- package/src/connectors/mcp/pilotServer.js +80 -0
- package/src/connectors/mcp/sanitizer.js +38 -0
- package/src/connectors/mcp/stdio.js +178 -0
- package/src/crawler/ipRanges.js +161 -0
- package/src/detectors/agent.js +127 -0
- package/src/detectors/ans.js +282 -0
- package/src/detectors/crawl.js +46 -0
- package/src/detectors/cro.js +825 -0
- package/src/detectors/cwv.js +38 -1
- package/src/detectors/evd.js +101 -0
- package/src/detectors/framework.js +32 -2
- package/src/detectors/geoReco.js +408 -0
- package/src/detectors/index.js +4 -2
- package/src/detectors/link.js +112 -1
- package/src/detectors/page.js +101 -0
- package/src/detectors/schemaData.js +529 -0
- package/src/detectors/tech.js +306 -0
- package/src/extractor/page.js +186 -2
- package/src/extractor/site.js +9 -2
- package/src/installer/index.js +45 -2
- package/src/monitoring/alertDelivery.js +101 -7
- package/src/observations/attribution.js +217 -0
- package/src/registries/index.js +14 -0
- package/src/reporting/consensus.js +416 -0
- package/src/reporting/executiveExport.js +244 -0
package/src/cli/index.js
CHANGED
|
@@ -1,14 +1,36 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import fs from 'node:fs';
|
|
2
3
|
import path from 'node:path';
|
|
3
4
|
import { pathToFileURL } from 'node:url';
|
|
4
5
|
import { init } from '../commands/init.js';
|
|
5
6
|
import { audit } from '../commands/audit.js';
|
|
7
|
+
import { auditEdgeCode } from '../commands/edgeSecurity.js';
|
|
6
8
|
import { planAudit } from '../commands/planAudit.js';
|
|
7
9
|
import { demo } from '../commands/demo.js';
|
|
8
10
|
import { validate } from '../commands/validate.js';
|
|
9
11
|
import { mapClaims } from '../commands/mapClaims.js';
|
|
10
12
|
import { substantiate } from '../commands/substantiate.js';
|
|
11
13
|
import { inspect } from '../commands/inspect.js';
|
|
14
|
+
import { inspectSerp } from '../commands/inspectSerp.js';
|
|
15
|
+
import { inspectCro } from '../commands/inspectCro.js';
|
|
16
|
+
import { inspectAeo } from '../commands/inspectAeo.js';
|
|
17
|
+
import { inspectGeo } from '../commands/inspectGeo.js';
|
|
18
|
+
import { testFunnel } from '../commands/testFunnel.js';
|
|
19
|
+
import { planExperiment } from '../commands/planExperiment.js';
|
|
20
|
+
import { fleetSummary, fleetAudit } from '../commands/fleet.js';
|
|
21
|
+
import { exportEdgeRules, testEdgeRules } from '../commands/edgeRules.js';
|
|
22
|
+
import { probeEngine } from '../commands/probe.js';
|
|
23
|
+
import { lintComponents } from '../commands/lintComponents.js';
|
|
24
|
+
import { exportExecutiveReport } from '../reporting/executiveExport.js';
|
|
25
|
+
import { attributeImpact } from '../commands/attributeImpact.js';
|
|
26
|
+
import { remediateCommand } from '../commands/remediate.js';
|
|
27
|
+
import { verifyRemediation } from '../commands/verifyRemediation.js';
|
|
28
|
+
import { exportImplementationKit } from '../commands/implementationKit.js';
|
|
29
|
+
import { compatibilityCommand, verifyPage } from '../commands/compatibility.js';
|
|
30
|
+
import { runVisualRegression } from '../commands/visualRegression.js';
|
|
31
|
+
import { checkExperiment } from '../commands/experimentGuardrails.js';
|
|
32
|
+
import { runGoldenBenchmark } from '../commands/goldenCorpus.js';
|
|
33
|
+
import { previewCroCommand } from '../commands/previewCro.js';
|
|
12
34
|
import { schemaCommand } from '../commands/schemaCmd.js';
|
|
13
35
|
import { compareSnapshots } from '../commands/compareSnapshots.js';
|
|
14
36
|
import { isInstallerCommand, runInstallerCommand } from '../installer/index.js';
|
|
@@ -19,9 +41,12 @@ import { applyRemediation } from '../commands/applyRemediation.js';
|
|
|
19
41
|
import { monitor, monitorAndAlert } from '../commands/monitor.js';
|
|
20
42
|
import { reportDashboard } from '../commands/reportDashboard.js';
|
|
21
43
|
import { reportShareOfVoice } from '../commands/reportShareOfVoice.js';
|
|
44
|
+
import { reportConsensus } from '../commands/reportConsensus.js';
|
|
22
45
|
import { evaluateObjective, importMetrics, initializeObjective, validateObjectives } from '../commands/measurement.js';
|
|
23
|
-
import { configureConnection, connectionStatus, discoverConnections, disconnectConnection, syncConnection, validateConnection, readCmsContent, applyCmsRemediation } from '../commands/connect.js';
|
|
46
|
+
import { configureConnection, connectionStatus, discoverConnections, disconnectConnection, syncConnection, validateConnection, readCmsContent, applyCmsRemediation, submitIndexNow, collectMcpEvidence } from '../commands/connect.js';
|
|
47
|
+
import { generateLlmsTxt } from '../commands/generateLlmsTxt.js';
|
|
24
48
|
import { evaluateDispositions, validateGovernance } from '../commands/governance.js';
|
|
49
|
+
import { listExceptions, renewException, invalidateException } from '../commands/exceptions.js';
|
|
25
50
|
import { evaluateReviews, initializeSamplingPlan, prioritizeReviews, queueReviews, selectSample } from '../commands/reviews.js';
|
|
26
51
|
import { selfUpgradeCommand, selfUpgradeExitCode } from '../commands/selfUpgrade.js';
|
|
27
52
|
// Executive reporting suite
|
|
@@ -52,13 +77,24 @@ Commands
|
|
|
52
77
|
check Report installed and available Citable versions
|
|
53
78
|
uninstall Remove managed Citable skill files
|
|
54
79
|
list List detected providers and installation state
|
|
55
|
-
doctor
|
|
80
|
+
doctor [--mcp] Diagnose installer, provider, MCP transport, and integrity problems
|
|
56
81
|
init [--seed <name>] Initialize .citable/ context and registries (non-destructive)
|
|
57
82
|
demo Run the detector engine against a bundled offline example (no setup, no network)
|
|
58
83
|
audit [scope] Run detectors; scopes: technical seo aeo geo architecture entity
|
|
59
84
|
claims evidence schema lifecycle corroboration
|
|
60
85
|
plan-audit Inspect target signals and propose an evidence-bounded audit plan
|
|
61
86
|
inspect <page> Profile one page (URL path or source file)
|
|
87
|
+
inspect serp <page> Simulate Google SERP title/snippet truncation and rich results
|
|
88
|
+
inspect cro <page> Evaluate page conversion readiness, CTA visibility, and friction
|
|
89
|
+
inspect saliency <page> Evaluate privacy-first algorithmic visual attention and CTA conspicuity
|
|
90
|
+
inspect aeo <page> Evaluate answer extractability, question density, and structure
|
|
91
|
+
inspect geo <page> Evaluate RAG chunkability, section density, and retrieval posture
|
|
92
|
+
preview cro <page> Interactive split-screen visual preview of original vs remediated page
|
|
93
|
+
remediate [finding] Synthesize accessible Nebula Components; safe patches with diff, validation, confidence, rollback (--write gated, fail closed)
|
|
94
|
+
test funnel [id] Test multi-step conversion funnel continuity and parameter persistence
|
|
95
|
+
test visual Deterministic layout-contract checks + viewport/variant screenshot matrix (screenshots need playwright)
|
|
96
|
+
plan experiment Calculate statistical sample size and duration for A/B testing
|
|
97
|
+
check experiment Experiment guardrails: SRM, stopping, power, contamination, lifecycle status
|
|
62
98
|
map-claims Extract material claim candidates from pages (--write to save)
|
|
63
99
|
substantiate Assess claim/evidence status (--write to apply downgrades)
|
|
64
100
|
schema Validate deployed JSON-LD and propose registry-derived schema
|
|
@@ -69,9 +105,10 @@ Commands
|
|
|
69
105
|
consensus, performance, corroboration, crawler or regional probes,
|
|
70
106
|
media evidence, or representation evidence
|
|
71
107
|
apply Apply a reviewed, hash-locked remediation spec
|
|
72
|
-
monitor [runA runB] Compare observation runs and emit regression alerts [--webhook <url>]
|
|
108
|
+
monitor [runA runB] Compare observation runs and emit regression alerts [--webhook <url>] [--min-severity <level>]
|
|
73
109
|
report dashboard [--last N] [--since <run-id>] Render a cross-run evidence trend as Markdown + HTML
|
|
74
110
|
report share-of-voice [--last N] Compute first-party and competitor citation share
|
|
111
|
+
report consensus [--last N] [--since <run-id>] Render Canonical Discovery Consensus Matrix (Markdown + HTML)
|
|
75
112
|
metrics import Import declared metric observations from CSV/JSON
|
|
76
113
|
connect status List optional connectors and configured connections
|
|
77
114
|
connect configure Configure non-secret connection state (--write to save)
|
|
@@ -81,11 +118,16 @@ Commands
|
|
|
81
118
|
connect read Read CMS content and hash for remediation targeting
|
|
82
119
|
connect apply Apply reviewed, hash-locked CMS remediation (--write to apply)
|
|
83
120
|
connect disconnect Remove optional connection state (--write to confirm)
|
|
121
|
+
connect indexnow Submit changed or discovered URLs to IndexNow engines (--write to submit)
|
|
122
|
+
connect mcp Collect evidence via allowlisted read-only MCP transport adapter
|
|
84
123
|
objectives init Validate/add one objective from --input (--write to save)
|
|
85
124
|
objectives validate Validate objective contracts and metric references
|
|
86
125
|
evaluate [objective-id] Compare objective baseline and evaluation windows
|
|
87
126
|
governance validate Validate reviewer, policy, and exception controls
|
|
88
127
|
governance evaluate [run] Produce immutable enforcement dispositions without changing findings
|
|
128
|
+
exceptions list List governed exceptions with expiry status [--expired]
|
|
129
|
+
exceptions renew Renew governed exception with reviewer/evidence controls (--write to save)
|
|
130
|
+
exceptions invalidate Revoke governed exception with mandatory audit reason (--write to save)
|
|
89
131
|
reviews queue [run policy] Create semantic review work from heuristic findings
|
|
90
132
|
reviews prioritize Rank review work from explicit materiality inputs
|
|
91
133
|
reviews plan Validate/add a sampling plan from --input
|
|
@@ -96,9 +138,14 @@ Commands
|
|
|
96
138
|
corpus evaluate Evaluate a disclosed real-property acceptance corpus
|
|
97
139
|
corpus publish Validate and project an owner-authorized public corpus
|
|
98
140
|
corpus receipt Create a reproducibility receipt for a sealed run
|
|
141
|
+
corpus benchmark Run the labeled golden fixture corpus and report per-detector precision/recall
|
|
99
142
|
corpus compare-receipts Compare two acceptance-run receipt envelopes
|
|
100
143
|
artifacts export Export one sealed run as a portable verified directory
|
|
101
144
|
artifacts verify Verify an exported artifact interchange directory
|
|
145
|
+
verify remediation Closed loop: re-run the detector after a patch and emit a before/after evidence bundle
|
|
146
|
+
verify page <page> Run all detectors scoped to one page and report posture (pass/attention/blocked)
|
|
147
|
+
compatibility Pre-flight: Node engine, optional adapters, browser, framework, registries, edge limits
|
|
148
|
+
kit export Customer-ready implementation kit: finding, diff, evidence, acceptance tests, deployment
|
|
102
149
|
artifacts import Import a verified run without changing its canonical bytes
|
|
103
150
|
self-upgrade Check for a newer version and upgrade the npx cache
|
|
104
151
|
kpi [list|show|validate] KPI architecture — govern metric definitions, sources, targets
|
|
@@ -141,6 +188,23 @@ Options
|
|
|
141
188
|
--lighthouse Run local, repeated Lighthouse lab observations
|
|
142
189
|
--ocr Explicitly request optional OCR for media images
|
|
143
190
|
--seed <name> Overlay a bundled starter registry seed (all entries default to unverified)
|
|
191
|
+
--urls <file|list> URL list or file for IndexNow submission
|
|
192
|
+
--sitemap <url|file> Sitemap URL or file for URL discovery and submission
|
|
193
|
+
--host <hostname> Target host for IndexNow submission
|
|
194
|
+
--key <key> IndexNow key (or INDEXNOW_KEY environment variable)
|
|
195
|
+
--key-location <url> Custom location of IndexNow key file
|
|
196
|
+
--skip-key-verify Skip pre-flight key verification HTTP probe
|
|
197
|
+
--server <id> Allowlisted MCP server identifier
|
|
198
|
+
--tool <name> Allowlisted read-only MCP tool name
|
|
199
|
+
--transport <stdio|http> MCP transport protocol (stdio or http)
|
|
200
|
+
--server-script <path> Local script path for stdio MCP server
|
|
201
|
+
--tool-args <json> JSON arguments string for MCP tool call
|
|
202
|
+
--until <YYYY-MM-DD> Target expiration date for exception renewal
|
|
203
|
+
--reason <text> Required justification for exception invalidation
|
|
204
|
+
--evidence <id> Evidence ID to link to exception renewal
|
|
205
|
+
--note <text> Optional note for exception renewal audit trail
|
|
206
|
+
--expired Filter exception list to expired records only
|
|
207
|
+
--expiring-soon <days> Threshold days to flag expiring exceptions (default: 14)
|
|
144
208
|
--write Persist registry changes (map-claims, substantiate)
|
|
145
209
|
--json Machine-readable output only
|
|
146
210
|
|
|
@@ -159,6 +223,7 @@ function parseArgs(argv) {
|
|
|
159
223
|
else if (a === '--target') args.target = argv[++i];
|
|
160
224
|
else if (a === '--base-url') args.baseUrl = argv[++i];
|
|
161
225
|
else if (a === '--ref-date') args.refDate = argv[++i];
|
|
226
|
+
else if (a === '--viewport') args.viewport = argv[++i];
|
|
162
227
|
else if (a === '--input') args.input = argv[++i];
|
|
163
228
|
else if (a === '--output') args.output = argv[++i];
|
|
164
229
|
else if (a === '--run') args.runId = argv[++i];
|
|
@@ -186,17 +251,77 @@ function parseArgs(argv) {
|
|
|
186
251
|
else if (a === '--webhook') args.webhook = argv[++i];
|
|
187
252
|
else if (a === '--min-severity') args.minSeverity = argv[++i];
|
|
188
253
|
else if (a === '--monitor') args.monitor = true;
|
|
254
|
+
else if (a === '--mcp') args.mcp = true;
|
|
189
255
|
else if (a === '--target-id') args.targetId = argv[++i];
|
|
190
256
|
else if (a === '--reviewer') args.reviewer = argv[++i];
|
|
191
257
|
else if (a === '--entity') args.entity = argv[++i];
|
|
258
|
+
else if (a === '--urls') args.urls = argv[++i];
|
|
259
|
+
else if (a === '--sitemap') args.sitemap = argv[++i];
|
|
260
|
+
else if (a === '--host') args.host = argv[++i];
|
|
261
|
+
else if (a === '--key') args.key = argv[++i];
|
|
262
|
+
else if (a === '--key-location') args.keyLocation = argv[++i];
|
|
263
|
+
else if (a === '--skip-key-verify') args.skipKeyVerify = true;
|
|
264
|
+
else if (a === '--allow-private-for-test') args.allowPrivateForTest = true;
|
|
265
|
+
else if (a === '--server') args.serverId = argv[++i];
|
|
266
|
+
else if (a === '--tool') args.toolName = argv[++i];
|
|
267
|
+
else if (a === '--transport') args.transportType = argv[++i];
|
|
268
|
+
else if (a === '--server-script') args.serverScript = argv[++i];
|
|
269
|
+
else if (a === '--tool-args' || a === '--args') args.toolArgs = argv[++i];
|
|
270
|
+
else if (a === '--site-url') args.siteUrl = argv[++i];
|
|
271
|
+
else if (a === '--until') args.until = argv[++i];
|
|
272
|
+
else if (a === '--reason') args.reason = argv[++i];
|
|
273
|
+
else if (a === '--id') args.id = argv[++i];
|
|
274
|
+
else if (a === '--evidence') args.evidence = argv[++i];
|
|
275
|
+
else if (a === '--note') args.note = argv[++i];
|
|
276
|
+
else if (a === '--expired') args.expiredOnly = true;
|
|
277
|
+
else if (a === '--expiring-soon') args.expiringSoonDays = Number(argv[++i]);
|
|
278
|
+
else if (a === '--finding') args.finding = argv[++i];
|
|
279
|
+
else if (a === '--component') args.component = argv[++i];
|
|
280
|
+
else if (a === '--format') args.format = argv[++i];
|
|
281
|
+
else if (a === '--strict') args.strict = true;
|
|
282
|
+
else if (a === '--export') args.export = argv[++i];
|
|
283
|
+
else if (a === '--apply') args.apply = true;
|
|
284
|
+
else if (a === '--preview-file') args['preview-file'] = argv[++i];
|
|
285
|
+
else if (a === '--observed-control') args['observed-control'] = argv[++i];
|
|
286
|
+
else if (a === '--observed-variant') args['observed-variant'] = argv[++i];
|
|
287
|
+
else if (a === '--days-running') args['days-running'] = argv[++i];
|
|
288
|
+
else if (a === '--baseline-rate') args['baseline-rate'] = argv[++i];
|
|
289
|
+
else if (a === '--p-value') args['p-value'] = argv[++i];
|
|
290
|
+
else if (a === '--revenue-claimed') args['revenue-claimed'] = true;
|
|
192
291
|
else args._.push(a);
|
|
193
292
|
}
|
|
194
293
|
return args;
|
|
195
294
|
}
|
|
196
295
|
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
296
|
+
const TOOL_VERSION_CLI = JSON.parse(
|
|
297
|
+
fs.readFileSync(new URL('../../package.json', import.meta.url), 'utf8')
|
|
298
|
+
).version;
|
|
299
|
+
|
|
300
|
+
// Set by main(); read by out() for the JSON envelope. Kept off `args` because
|
|
301
|
+
// several commands spread parsed args directly into option objects.
|
|
302
|
+
let CURRENT_COMMAND = null;
|
|
303
|
+
|
|
304
|
+
/**
|
|
305
|
+
* Stable machine-readable output contract (citable_output_schema 1.0):
|
|
306
|
+
* every command's --json payload is wrapped in this envelope so CI systems
|
|
307
|
+
* and agencies can consume results without parsing terminal text. The
|
|
308
|
+
* envelope version changes only on breaking envelope changes; command
|
|
309
|
+
* payloads evolve backward-compatibly inside `result`.
|
|
310
|
+
*/
|
|
311
|
+
const OUTPUT_SCHEMA_VERSION = '1.0';
|
|
312
|
+
|
|
313
|
+
function out(args, human, data, command = null) {
|
|
314
|
+
if (args.json) {
|
|
315
|
+
console.log(JSON.stringify({
|
|
316
|
+
citable_output_schema: OUTPUT_SCHEMA_VERSION,
|
|
317
|
+
tool_version: TOOL_VERSION_CLI,
|
|
318
|
+
command: command ?? CURRENT_COMMAND,
|
|
319
|
+
generated_at: new Date().toISOString(),
|
|
320
|
+
result: data,
|
|
321
|
+
}, null, 2));
|
|
322
|
+
} else {
|
|
323
|
+
console.log(human);
|
|
324
|
+
}
|
|
200
325
|
}
|
|
201
326
|
|
|
202
327
|
export async function main(argv = process.argv.slice(2), options = {}) {
|
|
@@ -207,6 +332,7 @@ export async function main(argv = process.argv.slice(2), options = {}) {
|
|
|
207
332
|
}
|
|
208
333
|
if (isInstallerCommand(cmd)) return runInstallerCommand(cmd, argv.slice(1), options);
|
|
209
334
|
const args = parseArgs(argv.slice(1));
|
|
335
|
+
CURRENT_COMMAND = cmd;
|
|
210
336
|
const root = options.cwd ?? process.cwd();
|
|
211
337
|
|
|
212
338
|
try {
|
|
@@ -234,8 +360,20 @@ export async function main(argv = process.argv.slice(2), options = {}) {
|
|
|
234
360
|
break;
|
|
235
361
|
}
|
|
236
362
|
case 'audit': {
|
|
363
|
+
if (args._[0] === 'edge') {
|
|
364
|
+
const file = args._[1];
|
|
365
|
+
if (!file || !args.format) throw new Error('usage: citable audit edge <file> --format <cloudflare-worker|cloudflare-cro|vercel-middleware|shopify-snippet|html>');
|
|
366
|
+
const content = fs.readFileSync(path.resolve(root, file), 'utf8');
|
|
367
|
+
const r = auditEdgeCode(content, { format: args.format, name: file });
|
|
368
|
+
const txt = `audit edge ${file} (${r.format}, ${r.byte_size} bytes)
|
|
369
|
+
Findings: ${r.finding_count} (high:${r.high} medium:${r.medium} advisory:${r.advisory})
|
|
370
|
+
${r.findings.map((f) => ` [${f.rule_id}] (${f.severity}) ${f.summary}\n Fix: ${f.remediation}`).join('\n') || ' no risk patterns found'}
|
|
371
|
+
Methodology: ${r.methodology}`;
|
|
372
|
+
out(args, txt, r);
|
|
373
|
+
break;
|
|
374
|
+
}
|
|
237
375
|
const scope = args._[0];
|
|
238
|
-
const r = await audit(root, { target: args.target, scope, baseUrl: args.baseUrl, refDate: args.refDate });
|
|
376
|
+
const r = await audit(root, { target: args.target, scope, baseUrl: args.baseUrl, refDate: args.refDate, viewport: args.viewport });
|
|
239
377
|
out(args, `Audit ${r.runId}: ${r.summary.total} finding(s) [${Object.entries(r.summary.by_severity).map(([k, v]) => `${k}:${v}`).join(' ')}]\nEvidence package: ${r.dir}\nReport: ${path.join(r.dir, 'report.md')}\nStatus: ${r.manifest.status}${r.manifest.incomplete_checks.length ? `\nIncomplete: ${r.manifest.incomplete_checks.join('; ')}` : ''}`, { runId: r.runId, dir: r.dir, summary: r.summary, status: r.manifest.status });
|
|
240
378
|
break;
|
|
241
379
|
}
|
|
@@ -247,11 +385,338 @@ export async function main(argv = process.argv.slice(2), options = {}) {
|
|
|
247
385
|
break;
|
|
248
386
|
}
|
|
249
387
|
case 'inspect': {
|
|
250
|
-
if (!args._[0]) throw new Error('usage: citable inspect <page> --target <dir|url>');
|
|
388
|
+
if (!args._[0]) throw new Error('usage: citable inspect <page> --target <dir|url> OR citable inspect serp <page> OR citable inspect cro <page> OR citable inspect aeo <page> OR citable inspect geo <page>');
|
|
389
|
+
if (args._[0] === 'serp') {
|
|
390
|
+
if (!args._[1]) throw new Error('usage: citable inspect serp <page> --target <dir|url>');
|
|
391
|
+
const r = await inspectSerp(root, args._[1], { target: args.target, baseUrl: args.baseUrl, refDate: args.refDate });
|
|
392
|
+
const richList = Object.entries(r.richSnippets).map(([k, v]) => ` ${k}: ${v.status}${v.issues?.length ? ` (${v.issues.join('; ')})` : ''}`).join('\n');
|
|
393
|
+
const txt = `Inspect SERP ${r.url}\n${r.visualCard}
|
|
394
|
+
Desktop Preview (max ~600px):
|
|
395
|
+
Breadcrumb: ${r.desktop.breadcrumb}
|
|
396
|
+
Title: ${r.desktop.title} (${r.desktop.titlePixelWidth}px${r.desktop.isTitleTruncated ? ' [TRUNCATED]' : ''})
|
|
397
|
+
Snippet: ${r.desktop.snippet}${r.desktop.isSnippetTruncated ? ' [TRUNCATED]' : ''}
|
|
398
|
+
Mobile Preview:
|
|
399
|
+
Breadcrumb: ${r.mobile.breadcrumb}
|
|
400
|
+
Title: ${r.mobile.title}${r.mobile.isTitleTruncated ? ' [TRUNCATED]' : ''}
|
|
401
|
+
Snippet: ${r.mobile.snippet}${r.mobile.isSnippetTruncated ? ' [TRUNCATED]' : ''}
|
|
402
|
+
Rich Snippet Eligibility:
|
|
403
|
+
${richList}
|
|
404
|
+
Schema Findings: ${r.findings.length ? r.findings.map((f) => `${f.detector_id} (${f.severity}): ${f.summary}`).join('; ') : 'none'}`;
|
|
405
|
+
out(args, txt, r);
|
|
406
|
+
break;
|
|
407
|
+
}
|
|
408
|
+
if (args._[0] === 'cro') {
|
|
409
|
+
if (!args._[1]) throw new Error('usage: citable inspect cro <page> --target <dir|url>');
|
|
410
|
+
const r = await inspectCro(root, args._[1], { target: args.target, baseUrl: args.baseUrl, refDate: args.refDate });
|
|
411
|
+
const txt = `Inspect CRO ${r.url}
|
|
412
|
+
Status: ${r.conversion_status}
|
|
413
|
+
Friction Surface Area: ${r.friction_surface_area}
|
|
414
|
+
Declared Conversion Action: ${r.declared_conversion_action || 'none'}
|
|
415
|
+
Page Type: ${r.page_type || 'unregistered'}; Intent: ${r.primary_intent || 'unregistered'}
|
|
416
|
+
Analytics Instrumentation: ${r.analytics_installed ? `INSTALLED (${r.analytics_tags.length} tag${r.analytics_tags.length === 1 ? '' : 's'})` : 'NOT DETECTED'}
|
|
417
|
+
Hero CTAs: ${r.hero_cta_count}; Choice Overload: ${r.has_choice_overload ? 'YES (paralysis risk)' : 'no'}
|
|
418
|
+
Primary CTA Conspicuity: ${r.saliency?.primary_cta_conspicuity_index ?? 'N/A'} (${r.saliency?.pci_assessment ?? 'unassessed'}) — modeled heuristic index, not observed attention
|
|
419
|
+
Keystroke Effort: ${r.keystroke_effort?.autofill_coverage_pct}% autofill coverage (${r.keystroke_effort?.keystroke_reduction_pct}% modeled mobile friction reduction)
|
|
420
|
+
Payment Wallet Readiness: ${r.express_checkout_readiness?.payment_wallet_readiness?.supported ? 'SUPPORTED' : 'NOT DETECTED (CRO-021)'}
|
|
421
|
+
Authentication Readiness (passkeys/WebAuthn): ${r.express_checkout_readiness?.authentication_readiness?.supported ? 'DETECTED' : 'NOT DETECTED'} (informational index; authentication, not payment)
|
|
422
|
+
CTAs (${r.ctas.length}):
|
|
423
|
+
${r.ctas.map((c) => ` [${c.isPrimary ? 'PRIMARY' : 'SECONDARY'}${c.inHero ? ' - HERO' : ''}] "${c.text}" (${c.tag}) → ${c.target || 'no target'}`).join('\n') || ' none'}
|
|
424
|
+
Forms (${r.forms.length}):
|
|
425
|
+
${r.forms.map((f, i) => ` Form ${i + 1}: ${f.fieldCount} fields (autocomplete: ${f.inputsWithAutocomplete}/${f.fieldCount}, labels: ${f.inputsWithLabel}/${f.fieldCount}), submit: ${f.hasSubmit ? 'yes' : 'NO'} → ${f.action || 'default'}`).join('\n') || ' none'}
|
|
426
|
+
Trust Signals: ${r.trustBadges.join(', ') || 'none'}
|
|
427
|
+
CRO Findings: ${r.findings.length ? r.findings.map((f) => `${f.detector_id} (${f.severity}): ${f.summary}`).join('; ') : 'none'}`;
|
|
428
|
+
out(args, txt, r);
|
|
429
|
+
break;
|
|
430
|
+
}
|
|
431
|
+
if (args._[0] === 'saliency') {
|
|
432
|
+
if (!args._[1]) throw new Error('usage: citable inspect saliency <page> --target <dir|url>');
|
|
433
|
+
const r = await inspectCro(root, args._[1], { target: args.target, baseUrl: args.baseUrl, refDate: args.refDate });
|
|
434
|
+
const sal = r.saliency;
|
|
435
|
+
const gazeLines = (sal?.predicted_gaze_path || []).map((g) => ` ${g.order}. [${g.type.toUpperCase()}] "${g.label}" (salience: ${Math.round(g.salience_score * 100)}%)`).join('\n');
|
|
436
|
+
const txt = `Inspect Saliency ${r.url}
|
|
437
|
+
Primary CTA Conspicuity Index: ${sal?.primary_cta_conspicuity_index ?? 'N/A'} [${sal?.pci_assessment ?? 'unassessed'}]
|
|
438
|
+
Visual Clutter Risk: ${sal?.visual_clutter ?? 'clean'} (${sal?.competing_hero_elements_count ?? 0} high-salience hero elements)
|
|
439
|
+
Predicted Gaze Path (First 3 Fixations):
|
|
440
|
+
${gazeLines || ' none predicted'}
|
|
441
|
+
SVG Attention Heatmap: generated (${sal?.elements_evaluated ?? 0} elements mapped)`;
|
|
442
|
+
out(args, txt, { url: r.url, saliency: sal });
|
|
443
|
+
break;
|
|
444
|
+
}
|
|
445
|
+
if (args._[0] === 'aeo') {
|
|
446
|
+
if (!args._[1]) throw new Error('usage: citable inspect aeo <page> --target <dir|url>');
|
|
447
|
+
const r = await inspectAeo(root, args._[1], { target: args.target, baseUrl: args.baseUrl, refDate: args.refDate });
|
|
448
|
+
const txt = `Inspect AEO ${r.url}
|
|
449
|
+
Extractability Status: ${r.extractability_status}
|
|
450
|
+
Page Type: ${r.page_type || 'unregistered'}; Word Count: ${r.word_count}
|
|
451
|
+
Headings (${r.headings.total} total, ${r.headings.questions.length} questions):
|
|
452
|
+
${r.headings.questions.map((q) => ` [H${q.level}] "${q.text}"`).join('\n') || ' none'}
|
|
453
|
+
Answer Structure:
|
|
454
|
+
Definitional Copula: ${r.answer_structure.has_copular_definition ? 'YES' : 'NO'}
|
|
455
|
+
Executive Summary / Key Takeaways: ${r.answer_structure.has_executive_summary ? 'YES' : 'NO'}
|
|
456
|
+
Tables: ${r.answer_structure.tables_count}; Ordered Lists: ${r.answer_structure.ordered_lists_count}
|
|
457
|
+
AEO Findings: ${r.findings.length ? r.findings.map((f) => `${f.detector_id} (${f.severity}): ${f.summary}`).join('; ') : 'none'}`;
|
|
458
|
+
out(args, txt, r);
|
|
459
|
+
break;
|
|
460
|
+
}
|
|
461
|
+
if (args._[0] === 'geo') {
|
|
462
|
+
if (!args._[1]) throw new Error('usage: citable inspect geo <page> --target <dir|url>');
|
|
463
|
+
const r = await inspectGeo(root, args._[1], { target: args.target, baseUrl: args.baseUrl, refDate: args.refDate });
|
|
464
|
+
const txt = `Inspect GEO ${r.url}
|
|
465
|
+
Retrieval Posture: ${r.retrieval_posture}
|
|
466
|
+
Page Type: ${r.page_type || 'unregistered'}; Word Count: ${r.word_count}
|
|
467
|
+
RAG Metrics:
|
|
468
|
+
Estimated 300w Chunks: ${r.rag_metrics.estimated_chunks_300w}
|
|
469
|
+
Headings: ${r.rag_metrics.total_headings}
|
|
470
|
+
Average Words/Chunk: ${r.rag_metrics.average_words_per_chunk}
|
|
471
|
+
GEO Findings: ${r.findings.length ? r.findings.map((f) => `${f.detector_id} (${f.severity}): ${f.summary}`).join('; ') : 'none'}`;
|
|
472
|
+
out(args, txt, r);
|
|
473
|
+
break;
|
|
474
|
+
}
|
|
251
475
|
const r = await inspect(root, args._[0], { target: args.target, baseUrl: args.baseUrl, refDate: args.refDate });
|
|
252
476
|
out(args, `Inspect ${r.url}\n title: ${r.title}\n status: ${r.status}; canonical: ${r.canonicals.join(', ') || 'none'}; robots: ${r.robotsDirectives.join(',') || 'default'}\n intent: ${r.primary_intent ?? 'undeclared'}; conversion: ${r.conversion_action ?? 'undeclared'}\n entities: ${r.entities.map((e) => e.name).join(', ') || 'none'}\n claims: ${r.claims.length}; findings: ${r.findings.length}\n ambiguity:\n${r.unresolved_ambiguity.map((a) => ` - ${a}`).join('\n') || ' none'}`, r);
|
|
253
477
|
break;
|
|
254
478
|
}
|
|
479
|
+
case 'inspect-serp': {
|
|
480
|
+
if (!args._[0]) throw new Error('usage: citable inspect-serp <page> --target <dir|url>');
|
|
481
|
+
const r = await inspectSerp(root, args._[0], { target: args.target, baseUrl: args.baseUrl, refDate: args.refDate });
|
|
482
|
+
const richList = Object.entries(r.richSnippets).map(([k, v]) => ` ${k}: ${v.status}${v.issues?.length ? ` (${v.issues.join('; ')})` : ''}`).join('\n');
|
|
483
|
+
const txt = `Inspect SERP ${r.url}\n${r.visualCard}
|
|
484
|
+
Desktop Preview (max ~600px):
|
|
485
|
+
Breadcrumb: ${r.desktop.breadcrumb}
|
|
486
|
+
Title: ${r.desktop.title} (${r.desktop.titlePixelWidth}px${r.desktop.isTitleTruncated ? ' [TRUNCATED]' : ''})
|
|
487
|
+
Snippet: ${r.desktop.snippet}${r.desktop.isSnippetTruncated ? ' [TRUNCATED]' : ''}
|
|
488
|
+
Mobile Preview:
|
|
489
|
+
Breadcrumb: ${r.mobile.breadcrumb}
|
|
490
|
+
Title: ${r.mobile.title}${r.mobile.isTitleTruncated ? ' [TRUNCATED]' : ''}
|
|
491
|
+
Snippet: ${r.mobile.snippet}${r.mobile.isSnippetTruncated ? ' [TRUNCATED]' : ''}
|
|
492
|
+
Rich Snippet Eligibility:
|
|
493
|
+
${richList}
|
|
494
|
+
Schema Findings: ${r.findings.length ? r.findings.map((f) => `${f.detector_id} (${f.severity}): ${f.summary}`).join('; ') : 'none'}`;
|
|
495
|
+
out(args, txt, r);
|
|
496
|
+
break;
|
|
497
|
+
}
|
|
498
|
+
case 'inspect-cro': {
|
|
499
|
+
if (!args._[0]) throw new Error('usage: citable inspect-cro <page> --target <dir|url>');
|
|
500
|
+
const r = await inspectCro(root, args._[0], { target: args.target, baseUrl: args.baseUrl, refDate: args.refDate });
|
|
501
|
+
const txt = `Inspect CRO ${r.url}
|
|
502
|
+
Status: ${r.conversion_status}
|
|
503
|
+
Declared Conversion Action: ${r.declared_conversion_action || 'none'}
|
|
504
|
+
Page Type: ${r.page_type || 'unregistered'}; Intent: ${r.primary_intent || 'unregistered'}
|
|
505
|
+
Analytics Instrumentation: ${r.analytics_installed ? `INSTALLED (${r.analytics_tags.length} tag${r.analytics_tags.length === 1 ? '' : 's'})` : 'NOT DETECTED'}
|
|
506
|
+
Hero CTAs: ${r.hero_cta_count}; Choice Overload: ${r.has_choice_overload ? 'YES (paralysis risk)' : 'no'}
|
|
507
|
+
CTAs (${r.ctas.length}):
|
|
508
|
+
${r.ctas.map((c) => ` [${c.isPrimary ? 'PRIMARY' : 'SECONDARY'}${c.inHero ? ' - HERO' : ''}] "${c.text}" (${c.tag}) → ${c.target || 'no target'}`).join('\n') || ' none'}
|
|
509
|
+
Forms (${r.forms.length}):
|
|
510
|
+
${r.forms.map((f, i) => ` Form ${i + 1}: ${f.fieldCount} fields (autocomplete: ${f.inputsWithAutocomplete}/${f.fieldCount}, labels: ${f.inputsWithLabel}/${f.fieldCount}), submit: ${f.hasSubmit ? 'yes' : 'NO'} → ${f.action || 'default'}`).join('\n') || ' none'}
|
|
511
|
+
Trust Signals: ${r.trustBadges.join(', ') || 'none'}
|
|
512
|
+
CRO Findings: ${r.findings.length ? r.findings.map((f) => `${f.detector_id} (${f.severity}): ${f.summary}`).join('; ') : 'none'}`;
|
|
513
|
+
out(args, txt, r);
|
|
514
|
+
break;
|
|
515
|
+
}
|
|
516
|
+
case 'inspect-aeo': {
|
|
517
|
+
if (!args._[0]) throw new Error('usage: citable inspect-aeo <page> --target <dir|url>');
|
|
518
|
+
const r = await inspectAeo(root, args._[0], { target: args.target, baseUrl: args.baseUrl, refDate: args.refDate });
|
|
519
|
+
const txt = `Inspect AEO ${r.url}
|
|
520
|
+
Extractability Status: ${r.extractability_status}
|
|
521
|
+
Page Type: ${r.page_type || 'unregistered'}; Word Count: ${r.word_count}
|
|
522
|
+
Headings (${r.headings.total} total, ${r.headings.questions.length} questions):
|
|
523
|
+
${r.headings.questions.map((q) => ` [H${q.level}] "${q.text}"`).join('\n') || ' none'}
|
|
524
|
+
Answer Structure:
|
|
525
|
+
Definitional Copula: ${r.answer_structure.has_copular_definition ? 'YES' : 'NO'}
|
|
526
|
+
Executive Summary / Key Takeaways: ${r.answer_structure.has_executive_summary ? 'YES' : 'NO'}
|
|
527
|
+
Tables: ${r.answer_structure.tables_count}; Ordered Lists: ${r.answer_structure.ordered_lists_count}
|
|
528
|
+
AEO Findings: ${r.findings.length ? r.findings.map((f) => `${f.detector_id} (${f.severity}): ${f.summary}`).join('; ') : 'none'}`;
|
|
529
|
+
out(args, txt, r);
|
|
530
|
+
break;
|
|
531
|
+
}
|
|
532
|
+
case 'inspect-geo': {
|
|
533
|
+
if (!args._[0]) throw new Error('usage: citable inspect-geo <page> --target <dir|url>');
|
|
534
|
+
const r = await inspectGeo(root, args._[0], { target: args.target, baseUrl: args.baseUrl, refDate: args.refDate });
|
|
535
|
+
const txt = `Inspect GEO ${r.url}
|
|
536
|
+
Retrieval Posture: ${r.retrieval_posture}
|
|
537
|
+
Page Type: ${r.page_type || 'unregistered'}; Word Count: ${r.word_count}
|
|
538
|
+
RAG Metrics:
|
|
539
|
+
Estimated 300w Chunks: ${r.rag_metrics.estimated_chunks_300w}
|
|
540
|
+
Headings: ${r.rag_metrics.total_headings}
|
|
541
|
+
Average Words/Chunk: ${r.rag_metrics.average_words_per_chunk}
|
|
542
|
+
GEO Findings: ${r.findings.length ? r.findings.map((f) => `${f.detector_id} (${f.severity}): ${f.summary}`).join('; ') : 'none'}`;
|
|
543
|
+
out(args, txt, r);
|
|
544
|
+
break;
|
|
545
|
+
}
|
|
546
|
+
case 'preview': {
|
|
547
|
+
const sub = args._[0];
|
|
548
|
+
if (sub !== 'cro') throw new Error('usage: citable preview cro <page> [--target <dir|url>] [--export <path>]');
|
|
549
|
+
const page = args._[1] || 'home';
|
|
550
|
+
const r = await previewCroCommand(root, page, { target: args.target, export: args.export });
|
|
551
|
+
const txt = `Preview CRO ${r.url}
|
|
552
|
+
Friction Surface Area (Before): ${r.fsa_before}
|
|
553
|
+
Friction Surface Area (Modeled Target): ${r.fsa_after} (modeled index, not a measured outcome)
|
|
554
|
+
Resolved Findings: ${r.findings_count}
|
|
555
|
+
HTML Preview: ${r.saved_file || 'rendered in memory (use --export <file.html> to save)'}`;
|
|
556
|
+
out(args, txt, r);
|
|
557
|
+
break;
|
|
558
|
+
}
|
|
559
|
+
case 'remediate': {
|
|
560
|
+
const r = await remediateCommand(root, {
|
|
561
|
+
finding: args.finding || args._[0],
|
|
562
|
+
component: args.component,
|
|
563
|
+
target: args.target,
|
|
564
|
+
format: args.format || 'react',
|
|
565
|
+
write: args.write,
|
|
566
|
+
});
|
|
567
|
+
if (r.target) {
|
|
568
|
+
const parts = [`Remediate ${r.finding_id} → ${r.recommended_component}`, ` Rationale: ${r.rationale}`, ` Scaffold Command: ${r.scaffold_command}`];
|
|
569
|
+
parts.push(` Target File: ${r.target}`);
|
|
570
|
+
parts.push(` Framework: ${r.framework?.framework || 'unknown'} (detection confidence ${r.framework?.confidence ?? 0})`);
|
|
571
|
+
parts.push(` Patch: ${r.changed ? `${r.match_count} match(es)` : 'no match (already remediated or not applicable)'}, idempotent: ${r.idempotent}, class: ${r.patcher_class}`);
|
|
572
|
+
if (r.validation) {
|
|
573
|
+
for (const c of r.validation.checks) parts.push(` Check ${c.check_id}: ${c.passed ? 'PASS' : 'FAIL'} — ${c.detail}`);
|
|
574
|
+
if (r.validation.methodology) parts.push(` Methodology: ${r.validation.methodology}`);
|
|
575
|
+
}
|
|
576
|
+
if (r.confidence) {
|
|
577
|
+
parts.push(` Confidence: ${r.confidence.score} (write threshold ${r.write_policy?.min_confidence ?? 0.7})`);
|
|
578
|
+
for (const f of r.confidence.factors) parts.push(` - ${f.factor}: ${f.weight} (${f.detail})`);
|
|
579
|
+
}
|
|
580
|
+
parts.push(r.written
|
|
581
|
+
? ` Written. Rollback snapshot: ${r.rollback?.snapshot_file}`
|
|
582
|
+
: r.write_refused
|
|
583
|
+
? ` Write REFUSED (fail closed): ${r.refusal_reason}`
|
|
584
|
+
: ` Dry run — unified diff below. Use --write to apply.`);
|
|
585
|
+
if (r.diff) parts.push('Unified Diff:', r.diff.trimEnd());
|
|
586
|
+
parts.push('', 'Code Snippet (recommended component):', '--------------------------------------------------', r.code, '--------------------------------------------------');
|
|
587
|
+
out(args, parts.join('\n'), r);
|
|
588
|
+
break;
|
|
589
|
+
}
|
|
590
|
+
const txt = r.recommended_component
|
|
591
|
+
? `Remediate ${r.finding_id} → ${r.recommended_component}
|
|
592
|
+
Rationale: ${r.rationale}
|
|
593
|
+
Scaffold Command: ${r.scaffold_command}
|
|
594
|
+
Code Snippet (${r.format}):
|
|
595
|
+
--------------------------------------------------
|
|
596
|
+
${r.code}
|
|
597
|
+
--------------------------------------------------`
|
|
598
|
+
: r.component
|
|
599
|
+
? `Remediate Component: ${r.component}
|
|
600
|
+
Scaffold Command: ${r.scaffold_command}
|
|
601
|
+
Code Snippet:
|
|
602
|
+
--------------------------------------------------
|
|
603
|
+
${r.code}
|
|
604
|
+
--------------------------------------------------`
|
|
605
|
+
: `Available Components:
|
|
606
|
+
${(r.available_components || []).map((c) => ` - ${c.name} (${c.id}): ${c.description}`).join('\n')}
|
|
607
|
+
Supported Findings: ${(r.supported_findings || []).join(', ')}`;
|
|
608
|
+
out(args, txt, r);
|
|
609
|
+
break;
|
|
610
|
+
}
|
|
611
|
+
case 'test-funnel':
|
|
612
|
+
case 'test': {
|
|
613
|
+
if (cmd === 'test' && args._[0] === 'visual') {
|
|
614
|
+
const preview = args['preview-file'] ? fs.readFileSync(path.resolve(root, args['preview-file']), 'utf8') : null;
|
|
615
|
+
const r = await runVisualRegression(root, { previewHtml: preview, output: args.output });
|
|
616
|
+
const txt = `test visual
|
|
617
|
+
Contract checks: ${r.contract.ok ? 'ALL PASS' : 'FAIL'} (${r.contract.checks.filter((c) => c.passed).length}/${r.contract.checks.length})
|
|
618
|
+
${r.contract.checks.map((c) => ` ${c.passed ? '✔' : '✖'} ${c.check_id}: ${c.detail}`).join('\n')}
|
|
619
|
+
Matrix cells: ${r.matrix_cells.length}
|
|
620
|
+
Screenshots: ${r.screenshots.status}${r.screenshots.required_input ? ` (required_input: ${r.screenshots.required_input})` : ` (${r.screenshots.captured.length} captured)`}`;
|
|
621
|
+
out(args, txt, r);
|
|
622
|
+
break;
|
|
623
|
+
}
|
|
624
|
+
if (cmd === 'test-funnel' || args._[0] === 'funnel') {
|
|
625
|
+
const funnelId = cmd === 'test-funnel' ? args._[0] : args._[1];
|
|
626
|
+
const r = await testFunnel(root, funnelId, { target: args.target, baseUrl: args.baseUrl, refDate: args.refDate });
|
|
627
|
+
const txt = `Test Funnel ${r.funnel_id} (${r.name})
|
|
628
|
+
Status: ${r.status}
|
|
629
|
+
Primary Goal: ${r.primary_goal}
|
|
630
|
+
Steps Verified: ${r.steps_verified}/${r.total_steps}
|
|
631
|
+
Steps:
|
|
632
|
+
${r.steps.map((s) => ` [${s.role.toUpperCase()}] ${s.name} (${s.url_pattern}) → HTTP ${s.status || 'not found'}${s.next_step_linked ? ' (linked)' : ''}`).join('\n')}
|
|
633
|
+
Issues (${r.issues.length}):
|
|
634
|
+
${r.issues.map((iss) => ` - ${iss}`).join('\n') || ' none'}
|
|
635
|
+
Funnel Findings: ${r.findings.length ? r.findings.map((f) => `${f.detector_id} (${f.severity}): ${f.summary}`).join('; ') : 'none'}`;
|
|
636
|
+
out(args, txt, r);
|
|
637
|
+
break;
|
|
638
|
+
}
|
|
639
|
+
if (args._[0] === 'edge') {
|
|
640
|
+
const r = await testEdgeRules(root, { provider: args.provider, baseUrl: args.baseUrl });
|
|
641
|
+
out(args, `Test Edge (${r.provider}): ${r.status.toUpperCase()} (${r.passed_checks}/${r.total_checks} checks passed)`, r);
|
|
642
|
+
break;
|
|
643
|
+
}
|
|
644
|
+
throw new Error('usage: citable test funnel [funnel_id] --target <dir|url> OR citable test edge [--provider cloudflare]');
|
|
645
|
+
}
|
|
646
|
+
case 'fleet': {
|
|
647
|
+
const sub = args._[0] ?? 'summary';
|
|
648
|
+
if (sub === 'summary') {
|
|
649
|
+
const r = await fleetSummary(root);
|
|
650
|
+
out(args, `Fleet Summary (${r.total_properties} properties, ${r.active_properties} active)\n` + r.properties.map((p) => ` [${p.environment.toUpperCase()}] ${p.name} (${p.primary_domain})`).join('\n'), r);
|
|
651
|
+
} else if (sub === 'audit') {
|
|
652
|
+
const r = await fleetAudit(root, { propertyId: args.property, environment: args.env, scope: args.scope });
|
|
653
|
+
out(args, `Fleet Audit: ${r.fleet_size} properties audited (health: ${r.fleet_health.toUpperCase()})\nTotal findings: ${r.total_findings} (critical: ${r.total_critical}, high: ${r.total_high})`, r);
|
|
654
|
+
} else {
|
|
655
|
+
throw new Error('usage: citable fleet [summary|audit] [--property <id>] [--env <environment>]');
|
|
656
|
+
}
|
|
657
|
+
break;
|
|
658
|
+
}
|
|
659
|
+
case 'probe': {
|
|
660
|
+
const query = args._[0];
|
|
661
|
+
if (!query) throw new Error('usage: citable probe <query|prompt_id> [--engine <perplexity|openai|gemini>] [--domain <domain>]');
|
|
662
|
+
const r = await probeEngine(root, query, { engine: args.engine, targetDomain: args.domain, output: args.output });
|
|
663
|
+
out(args, `Probe [${r.data.provider}]: ${r.data.property_cited ? 'CITING' : 'NOT CITING'} ${r.data.target_domain}\n${r.data.raw_response}`, r);
|
|
664
|
+
break;
|
|
665
|
+
}
|
|
666
|
+
case 'lint': {
|
|
667
|
+
const sub = args._[0];
|
|
668
|
+
if (sub === 'components') {
|
|
669
|
+
const dir = args._[1] || 'src/components';
|
|
670
|
+
const r = await lintComponents(dir);
|
|
671
|
+
out(args, `Lint Components: ${r.total_files_scanned} files scanned (${r.status.toUpperCase()})\nFindings: ${r.total_findings}\n` + r.findings.map((f) => ` [${f.rule_id}] ${f.file}:${f.line} - ${f.name}`).join('\n'), r);
|
|
672
|
+
break;
|
|
673
|
+
}
|
|
674
|
+
throw new Error('usage: citable lint components [dir]');
|
|
675
|
+
}
|
|
676
|
+
case 'export': {
|
|
677
|
+
const sub = args._[0];
|
|
678
|
+
if (sub === 'edge') {
|
|
679
|
+
const format = args.format || 'cloudflare-redirects';
|
|
680
|
+
const r = await exportEdgeRules(root, { format, output: args.output });
|
|
681
|
+
const sec = r.security;
|
|
682
|
+
out(args, `Exported ${r.rules_count} edge rule(s) in format "${r.format}"${r.output_path ? ` to ${r.output_path}` : ''}\nSecurity audit: ${sec.high} high, ${sec.medium} medium, ${sec.advisory} advisory finding(s)\n${sec.findings.map((f) => ` [${f.rule_id}] (${f.severity}) ${f.summary}`).join('\n') || ' no risk patterns found'}\n${r.content}`, r);
|
|
683
|
+
break;
|
|
684
|
+
}
|
|
685
|
+
throw new Error('usage: citable export edge [--format <cloudflare-redirects|cloudflare-waf|cloudflare-worker>]');
|
|
686
|
+
}
|
|
687
|
+
case 'attribute': {
|
|
688
|
+
const sub = args._[0];
|
|
689
|
+
if (sub === 'impact') {
|
|
690
|
+
const r = await attributeImpact(root, { beforeRunId: args.before, afterRunId: args.after, metricKey: args.metric, baselineValue: args.baseline ? Number(args.baseline) : undefined, observedValue: args.observed ? Number(args.observed) : undefined });
|
|
691
|
+
out(args, `Attribute Impact (${r.metric}):\n Treatment Lift: ${r.treatment.relative_change_percent}%\n Control Lift: ${r.control.relative_change_percent}%\n Diff-in-Diff Adjusted Lift: ${r.difference_in_differences_lift_percent}%\n Verdict: ${r.attribution_verdict}\n\nNotice: ${r.statistical_caveat}`, r);
|
|
692
|
+
break;
|
|
693
|
+
}
|
|
694
|
+
throw new Error('usage: citable attribute impact --before <run> --after <run>');
|
|
695
|
+
}
|
|
696
|
+
case 'plan-experiment':
|
|
697
|
+
case 'plan': {
|
|
698
|
+
if (cmd === 'plan-experiment' || args._[0] === 'experiment') {
|
|
699
|
+
const r = await planExperiment(root, args);
|
|
700
|
+
const txt = `Plan A/B Experiment
|
|
701
|
+
Baseline Conversion Rate: ${(r.baseline_conversion_rate * 100).toFixed(1)}%
|
|
702
|
+
Target Conversion Rate: ${(r.target_conversion_rate * 100).toFixed(2)}% (+${Math.round(r.minimum_detectable_effect_relative * 100)}% relative lift)
|
|
703
|
+
Statistical Power: ${Math.round(r.statistical_power * 100)}% (alpha = ${r.significance_level_alpha})
|
|
704
|
+
Sample Size Required: ${r.sample_size_per_variant.toLocaleString()} per variant (${r.total_sample_size.toLocaleString()} total)
|
|
705
|
+
Daily Visitors: ${r.daily_visitors.toLocaleString()}
|
|
706
|
+
Estimated Duration: ${r.estimated_duration_days} days
|
|
707
|
+
Risk Level: ${r.risk_level.toUpperCase()}
|
|
708
|
+
Recommendations:
|
|
709
|
+
${r.recommendations.map((rec) => ` - ${rec}`).join('\n')}`;
|
|
710
|
+
out(args, txt, r);
|
|
711
|
+
break;
|
|
712
|
+
}
|
|
713
|
+
if (cmd === 'plan' && args._[0] === 'audit') {
|
|
714
|
+
const p = await planAudit(root, { target: args.target, baseUrl: args.baseUrl, refDate: args.refDate });
|
|
715
|
+
out(args, formatPlan(p), p);
|
|
716
|
+
break;
|
|
717
|
+
}
|
|
718
|
+
throw new Error('usage: citable plan experiment --baseline-rate <0.02> --mde <0.10> --daily-visitors <500> OR citable plan-audit');
|
|
719
|
+
}
|
|
255
720
|
case 'map-claims': {
|
|
256
721
|
const r = await mapClaims(root, { target: args.target, baseUrl: args.baseUrl, write: args.write, refDate: args.refDate });
|
|
257
722
|
out(args, `map-claims: ${r.candidates.length} candidate claim(s)${args.write ? `, ${r.written} written to registry as candidate/review_required` : ' (dry run — use --write to save)'}\n` + r.candidates.map((c) => ` [${c.claim_type}${c.review_required ? ', REVIEW REQUIRED' : ''}] ${c.claim.slice(0, 100)}\n at ${c.source_location}`).join('\n'), r);
|
|
@@ -306,12 +771,17 @@ export async function main(argv = process.argv.slice(2), options = {}) {
|
|
|
306
771
|
runB: args._[1],
|
|
307
772
|
webhookUrl: args.webhook,
|
|
308
773
|
minSeverity: args.minSeverity,
|
|
774
|
+
allowPrivateForTest: args.allowPrivateForTest,
|
|
309
775
|
});
|
|
310
776
|
let msg = `monitor ${r.run_a} → ${r.run_b}: ${r.summary.alerts} alert(s), ${r.summary.critical_or_high} critical/high\nReport: ${path.join(r.dir, 'latest.json')}`;
|
|
311
777
|
if (r.delivery) {
|
|
312
778
|
if (r.delivery.skipped) msg += `\nDelivery: skipped (${r.delivery.reason})`;
|
|
313
|
-
else if (r.delivery.success)
|
|
314
|
-
|
|
779
|
+
else if (r.delivery.success) {
|
|
780
|
+
msg += `\nDelivery: webhook dispatched to ${r.delivery.target_url} (HTTP ${r.delivery.status_code})`;
|
|
781
|
+
if (r.delivery.signature) msg += ` (signed: ${r.delivery.signature.slice(0, 16)}...)`;
|
|
782
|
+
} else {
|
|
783
|
+
msg += `\nDelivery: webhook FAILED (${r.delivery.error})`;
|
|
784
|
+
}
|
|
315
785
|
}
|
|
316
786
|
out(args, msg, r);
|
|
317
787
|
if (r.summary.critical_or_high > 0) process.exitCode = 1;
|
|
@@ -325,8 +795,14 @@ export async function main(argv = process.argv.slice(2), options = {}) {
|
|
|
325
795
|
} else if (sub === 'share-of-voice' || sub === 'citations' || sub === 'share') {
|
|
326
796
|
const r = reportShareOfVoice(root, { since: args.since, last: args.last ? Number(args.last) : undefined });
|
|
327
797
|
out(args, `report share-of-voice: ${r.included} citation run(s) included, ${r.competitors_evaluated} competitor(s) evaluated\nMarkdown: ${r.path_md}\nHTML: ${r.path_html}`, r);
|
|
798
|
+
} else if (sub === 'consensus') {
|
|
799
|
+
const r = reportConsensus(root, { runId: args.runId, since: args.since, last: args.last ? Number(args.last) : undefined });
|
|
800
|
+
out(args, `report consensus: ${r.urls_evaluated} URL(s) evaluated (${r.canonical_consensus_count} consensus, ${r.conflicts_count} conflict(s))\nMarkdown: ${r.path_md}\nHTML: ${r.path_html}`, r);
|
|
801
|
+
} else if (sub === 'export') {
|
|
802
|
+
const r = await exportExecutiveReport(root, args.runId, { format: args.format, clientName: args.client, output: args.output });
|
|
803
|
+
out(args, `Executive Report Exported (${r.format}) for "${r.client_name}"${r.output_path ? ` to ${r.output_path}` : ''}\n\n${r.content}`, r);
|
|
328
804
|
} else {
|
|
329
|
-
throw new Error('usage: citable report <dashboard|share-of-voice> [--last <n>] [--since <run-id>]');
|
|
805
|
+
throw new Error('usage: citable report <dashboard|share-of-voice|consensus|export> [--last <n>] [--since <run-id>] [--run <run-id>] [--format <html-brief|markdown-deck>]');
|
|
330
806
|
}
|
|
331
807
|
break;
|
|
332
808
|
}
|
|
@@ -363,7 +839,41 @@ export async function main(argv = process.argv.slice(2), options = {}) {
|
|
|
363
839
|
} else if (mode === 'disconnect') {
|
|
364
840
|
const r = disconnectConnection(root, args);
|
|
365
841
|
out(args, `connect disconnect ${r.connection_id}: ${r.disconnected ? 'removed' : 'dry run; use --write to remove'}`, r);
|
|
366
|
-
} else
|
|
842
|
+
} else if (mode === 'indexnow') {
|
|
843
|
+
const r = await submitIndexNow(root, args);
|
|
844
|
+
const batchInfo = r.batch_count > 1 ? ` in ${r.batch_count} batch(es)` : '';
|
|
845
|
+
const actionDesc = r.dry_run ? '(dry run; use --write to submit)' : (r.success ? `submitted (status: ${r.batches[0]?.status_code ?? 200})` : `FAILED (${r.error})`);
|
|
846
|
+
out(args, `connect indexnow: ${r.url_count} URL(s)${batchInfo} for ${r.host} ${actionDesc}\nReceipt: ${r.receipt_file}`, r);
|
|
847
|
+
if (!r.success) process.exitCode = 1;
|
|
848
|
+
} else if (mode === 'mcp') {
|
|
849
|
+
const toolArgs = args.toolArgs ? JSON.parse(args.toolArgs) : (args.target ? { url: args.target } : (args.urls ? { url: args.urls } : {}));
|
|
850
|
+
if (args.target && !toolArgs.url) toolArgs.url = args.target;
|
|
851
|
+
if (args.siteUrl && !toolArgs.siteUrl) toolArgs.siteUrl = args.siteUrl;
|
|
852
|
+
else if (args.target && !toolArgs.siteUrl && args.serverId === 'gsc-mcp') {
|
|
853
|
+
try { toolArgs.siteUrl = new URL(args.target).origin; } catch { toolArgs.siteUrl = args.target; }
|
|
854
|
+
}
|
|
855
|
+
if (args.startDate && !toolArgs.startDate) toolArgs.startDate = args.startDate;
|
|
856
|
+
if (args.endDate && !toolArgs.endDate) toolArgs.endDate = args.endDate;
|
|
857
|
+
const r = await collectMcpEvidence(root, {
|
|
858
|
+
...args,
|
|
859
|
+
args: toolArgs,
|
|
860
|
+
});
|
|
861
|
+
out(args, `connect mcp: tool "${r.transportEnvelope.tool.name}" on server "${r.transportEnvelope.server.server_id}" executed via ${r.transportEnvelope.transport.type} (status: ${r.transportEnvelope.status})\nRun: ${r.runId}\nPackage: ${r.dir}`, r);
|
|
862
|
+
if (r.transportEnvelope.status !== 'success') process.exitCode = 1;
|
|
863
|
+
} else throw new Error('usage: citable connect <status|configure|discover|validate|sync|read|apply|disconnect|indexnow|mcp> [options]');
|
|
864
|
+
break;
|
|
865
|
+
}
|
|
866
|
+
case 'generate': {
|
|
867
|
+
const sub = args._[0];
|
|
868
|
+
if (sub === 'llms-txt' || sub === 'llms.txt') {
|
|
869
|
+
const r = generateLlmsTxt(root, { output: args.output, siteUrl: args.siteUrl, write: args.write });
|
|
870
|
+
out(args, `generate llms-txt: ${r.written ? 'written' : 'dry run (use --write to save)'}\nOutput: ${r.outputDir}\nPages indexed: ${r.pageCount}\nClaims declared: ${r.claimCount}`, r);
|
|
871
|
+
} else throw new Error('usage: citable generate <llms-txt> [--output <path>] [--site-url <url>] [--write]');
|
|
872
|
+
break;
|
|
873
|
+
}
|
|
874
|
+
case 'generate-llms-txt': {
|
|
875
|
+
const r = generateLlmsTxt(root, { output: args.output, siteUrl: args.siteUrl, write: args.write });
|
|
876
|
+
out(args, `generate llms-txt: ${r.written ? 'written' : 'dry run (use --write to save)'}\nOutput: ${r.outputDir}\nPages indexed: ${r.pageCount}\nClaims declared: ${r.claimCount}`, r);
|
|
367
877
|
break;
|
|
368
878
|
}
|
|
369
879
|
case 'objectives': {
|
|
@@ -397,6 +907,40 @@ export async function main(argv = process.argv.slice(2), options = {}) {
|
|
|
397
907
|
} else throw new Error('usage: citable governance <validate|evaluate [run-id]> [--ref-date YYYY-MM-DD]');
|
|
398
908
|
break;
|
|
399
909
|
}
|
|
910
|
+
case 'exceptions': {
|
|
911
|
+
const mode = args._[0];
|
|
912
|
+
if (mode === 'list') {
|
|
913
|
+
const r = listExceptions(root, {
|
|
914
|
+
expiredOnly: args.expiredOnly,
|
|
915
|
+
expiringSoonDays: args.expiringSoonDays,
|
|
916
|
+
refDate: args.refDate,
|
|
917
|
+
});
|
|
918
|
+
const rows = r.exceptions.map((e) =>
|
|
919
|
+
` ${e.exception_id} [${e.computed_status}]: expires ${e.expires_at.slice(0, 10)} (${e.days_remaining}d remaining, renewed ${e.renewal_count}/${e.renewal_limit}) - ${e.reason}`
|
|
920
|
+
);
|
|
921
|
+
out(args, `exceptions list: ${r.exceptions.length} exception(s) (total: ${r.summary.total}, active: ${r.summary.active}, expiring soon: ${r.summary.expiring_soon}, expired: ${r.summary.expired})\n` + (rows.length ? rows.join('\n') : ' (no matching exceptions)'), r);
|
|
922
|
+
} else if (mode === 'renew') {
|
|
923
|
+
const r = renewException(root, {
|
|
924
|
+
id: args.id || args._[1],
|
|
925
|
+
until: args.until,
|
|
926
|
+
reviewer: args.reviewer,
|
|
927
|
+
evidence: args.evidence,
|
|
928
|
+
note: args.note || args.reason,
|
|
929
|
+
write: args.write,
|
|
930
|
+
refDate: args.refDate,
|
|
931
|
+
});
|
|
932
|
+
out(args, `exceptions renew: ${r.exception_id} extended to ${r.expires_at} (renewal count: ${r.renewal_count}/${r.renewal_limit}) ${r.written ? 'written' : '(dry run; use --write to save)'}`, r);
|
|
933
|
+
} else if (mode === 'invalidate') {
|
|
934
|
+
const r = invalidateException(root, {
|
|
935
|
+
id: args.id || args._[1],
|
|
936
|
+
reason: args.reason,
|
|
937
|
+
reviewer: args.reviewer,
|
|
938
|
+
write: args.write,
|
|
939
|
+
});
|
|
940
|
+
out(args, `exceptions invalidate: ${r.exception_id} marked revoked ${r.written ? 'written' : '(dry run; use --write to save)'}`, r);
|
|
941
|
+
} else throw new Error('usage: citable exceptions <list|renew|invalidate> [options]');
|
|
942
|
+
break;
|
|
943
|
+
}
|
|
400
944
|
case 'reviews': {
|
|
401
945
|
const mode=args._[0]; let r;
|
|
402
946
|
if(mode==='queue') r=queueReviews(root,{runId:args._[1],policyId:args._[2],write:args.write});
|
|
@@ -456,7 +1000,14 @@ export async function main(argv = process.argv.slice(2), options = {}) {
|
|
|
456
1000
|
} else if (mode === 'compare-receipts') {
|
|
457
1001
|
const r = readAndCompareAcceptanceReceipts(args._[1], args._[2]);
|
|
458
1002
|
out(args, `corpus compare-receipts ${r.receipt_a} → ${r.receipt_b}\nComparable envelope: ${r.comparable}\nFingerprint equal: ${r.fingerprint_equal}\nPartial runs: ${r.partial_runs.length}`, r);
|
|
459
|
-
} else
|
|
1003
|
+
} else if (mode === 'benchmark') {
|
|
1004
|
+
const r = await runGoldenBenchmark(root, { corpusDir: args.input, writeReport: args.output });
|
|
1005
|
+
const txt = `corpus benchmark v${r.corpus_version}: ${r.pages_evaluated} page(s) across ${r.sites_evaluated} site(s)
|
|
1006
|
+
Gate: ${r.gate.ok ? 'PASS' : 'FAIL'} (violations: ${r.gate.violation_count}, recall failures: ${r.gate.recall_failures.join(', ') || 'none'}, precision failures: ${r.gate.precision_failures.join(', ') || 'none'})
|
|
1007
|
+
${r.per_detector.filter((d) => d.true_positives + d.false_negatives + d.false_positives > 0).map((d) => ` ${d.detector_id}: TP ${d.true_positives}, FP ${d.false_positives}, FN ${d.false_negatives}, P ${d.precision ?? 'n/a'}, R ${d.recall ?? 'n/a'}`).join('\n')}
|
|
1008
|
+
Limitations: ${r.limitations[0]}`;
|
|
1009
|
+
out(args, txt, r);
|
|
1010
|
+
} else throw new Error('usage: citable corpus <evaluate|publish|receipt|compare-receipts|benchmark> [options]');
|
|
460
1011
|
break;
|
|
461
1012
|
}
|
|
462
1013
|
case 'artifacts': {
|
|
@@ -473,6 +1024,93 @@ export async function main(argv = process.argv.slice(2), options = {}) {
|
|
|
473
1024
|
} else throw new Error('usage: citable artifacts <export <run-id> --output <directory>|verify --input <directory>|import --input <directory>>');
|
|
474
1025
|
break;
|
|
475
1026
|
}
|
|
1027
|
+
case 'verify': {
|
|
1028
|
+
const mode = args._[0];
|
|
1029
|
+
if (mode === 'page') {
|
|
1030
|
+
const r = await verifyPage(root, args._[1], { target: args.target, baseUrl: args.baseUrl, refDate: args.refDate, viewport: args.viewport });
|
|
1031
|
+
const sev = Object.entries(r.by_severity).filter(([, n]) => n > 0).map(([k, n]) => `${k}:${n}`).join(' ') || 'none';
|
|
1032
|
+
const txt = `verify page ${r.page}
|
|
1033
|
+
Status: ${r.status}
|
|
1034
|
+
Findings: ${r.finding_count} (${sev})
|
|
1035
|
+
Detectors run: ${r.provenance.detectors_run.length}
|
|
1036
|
+
Definition: ${r.provenance.definition}`;
|
|
1037
|
+
out(args, txt, r);
|
|
1038
|
+
break;
|
|
1039
|
+
}
|
|
1040
|
+
if (mode !== 'remediation') throw new Error('usage: citable verify <remediation --run <run-id> --finding <detector-id> [|page <page>] [--target <dir|file>] [--apply]');
|
|
1041
|
+
const r = await verifyRemediation(root, {
|
|
1042
|
+
run: args.runId || args._[1],
|
|
1043
|
+
finding: args.finding,
|
|
1044
|
+
target: args.target,
|
|
1045
|
+
apply: args.apply,
|
|
1046
|
+
subject: args.subject,
|
|
1047
|
+
baseUrl: args['base-url'],
|
|
1048
|
+
refDate: args['ref-date'],
|
|
1049
|
+
});
|
|
1050
|
+
const subject = r.subject?.identifier || 'unknown subject';
|
|
1051
|
+
const patchLine = r.patch
|
|
1052
|
+
? `\n Patch: applied=${r.patch.applied}${r.patch.refusal_reason ? ` (refused: ${r.patch.refusal_reason})` : ''}${r.patch.rollback_snapshot ? `\n Rollback snapshot: ${r.patch.rollback_snapshot}` : ''}`
|
|
1053
|
+
: '';
|
|
1054
|
+
const txt = `verify remediation ${r.detector_id} on ${subject}
|
|
1055
|
+
Verdict: ${r.status}${r.verdict.resolved ? ' (finding no longer reported)' : ''}
|
|
1056
|
+
Before finding(s): ${r.verdict.before_finding_ids.join(', ') || 'none'}
|
|
1057
|
+
After finding(s): ${r.verdict.after_finding_ids.join(', ') || 'none'}
|
|
1058
|
+
New critical/high regressions: ${r.verdict.new_regressions.length}${patchLine}
|
|
1059
|
+
Re-check target: ${r.provenance.recheck_target || 'n/a'}
|
|
1060
|
+
Resolution criterion: detector absence for the same subject; not an outcome guarantee
|
|
1061
|
+
Bundle: ${r.bundle_dir || 'not written'}`;
|
|
1062
|
+
out(args, txt, r);
|
|
1063
|
+
break;
|
|
1064
|
+
}
|
|
1065
|
+
case 'kit': {
|
|
1066
|
+
if (args._[0] !== 'export') throw new Error('usage: citable kit export --run <run-id> --finding <detector-id> [--target <file>] [--subject <id|url>] [--output <dir>]');
|
|
1067
|
+
const r = await exportImplementationKit(root, {
|
|
1068
|
+
run: args.runId,
|
|
1069
|
+
finding: args.finding,
|
|
1070
|
+
target: args.target,
|
|
1071
|
+
subject: args.subject,
|
|
1072
|
+
output: args.output,
|
|
1073
|
+
format: args.format,
|
|
1074
|
+
});
|
|
1075
|
+
const txt = `kit export ${r.detector_id}
|
|
1076
|
+
Kit directory: ${r.kit_dir}
|
|
1077
|
+
Files: ${r.files.length}
|
|
1078
|
+
Rendering evidence files: ${r.rendering_evidence_count}
|
|
1079
|
+
Patch diff included: ${r.has_patch_diff}`;
|
|
1080
|
+
out(args, txt, r);
|
|
1081
|
+
break;
|
|
1082
|
+
}
|
|
1083
|
+
case 'compatibility': {
|
|
1084
|
+
const r = await compatibilityCommand(root, args);
|
|
1085
|
+
const txt = `compatibility (citable ${r.tool_version}, Node ${r.node})
|
|
1086
|
+
${r.checks.map((c) => ` [${c.severity.toUpperCase()}] ${c.check_id}: ${c.detail}`).join('\n')}
|
|
1087
|
+
Blockers: ${r.blocker_count}
|
|
1088
|
+
Note: ${r.note}`;
|
|
1089
|
+
out(args, txt, r);
|
|
1090
|
+
break;
|
|
1091
|
+
}
|
|
1092
|
+
case 'check': {
|
|
1093
|
+
if (args._[0] === 'experiment') {
|
|
1094
|
+
const r = await checkExperiment(root, {
|
|
1095
|
+
experimentId: args._[1],
|
|
1096
|
+
observedControl: args['observed-control'] !== undefined ? Number(args['observed-control']) : undefined,
|
|
1097
|
+
observedVariant: args['observed-variant'] !== undefined ? Number(args['observed-variant']) : undefined,
|
|
1098
|
+
daysRunning: args['days-running'] !== undefined ? Number(args['days-running']) : undefined,
|
|
1099
|
+
baselineRate: args['baseline-rate'],
|
|
1100
|
+
mde: args.mde,
|
|
1101
|
+
pValue: args['p-value'],
|
|
1102
|
+
revenueClaimed: args['revenue-claimed'],
|
|
1103
|
+
});
|
|
1104
|
+
const txt = `check experiment ${r.experiment_id}
|
|
1105
|
+
Lifecycle: ${r.lifecycle} — ${r.lifecycle_definition}
|
|
1106
|
+
Guardrails: ${r.guardrail_count} finding(s)
|
|
1107
|
+
${r.findings.map((f) => ` [${f.guardrail_id}] (${f.severity}) ${f.summary}`).join('\n') || ' no guardrail findings'}
|
|
1108
|
+
Note: ${r.note}`;
|
|
1109
|
+
out(args, txt, r);
|
|
1110
|
+
break;
|
|
1111
|
+
}
|
|
1112
|
+
throw new Error('usage: citable check experiment <id> [--observed-control N --observed-variant N --days-running N --p-value X --revenue-claimed]');
|
|
1113
|
+
}
|
|
476
1114
|
case 'self-upgrade': {
|
|
477
1115
|
const output = await selfUpgradeCommand(argv.slice(1));
|
|
478
1116
|
console.log(output);
|