@nebulacomponents/citable 1.15.1 → 1.16.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 +25 -0
- package/README.md +3 -3
- package/dist/universal/.agents/skills/citable/SKILL.md +5 -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 +14 -11
- package/dist/universal/.agents/skills/citable/schemas/experiment.schema.json +1 -1
- 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/.claude/agents/citable/manifest.json +2 -2
- package/dist/universal/.claude/skills/citable/SKILL.md +5 -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 +14 -11
- package/dist/universal/.claude/skills/citable/schemas/experiment.schema.json +1 -1
- 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/.cursor/skills/citable/SKILL.md +5 -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 +14 -11
- package/dist/universal/.cursor/skills/citable/schemas/experiment.schema.json +1 -1
- 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/.gemini/skills/citable/SKILL.md +5 -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 +14 -11
- package/dist/universal/.gemini/skills/citable/schemas/experiment.schema.json +1 -1
- 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/.github/skills/citable/SKILL.md +5 -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 +14 -11
- package/dist/universal/.github/skills/citable/schemas/experiment.schema.json +1 -1
- 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/.kiro/skills/citable/SKILL.md +5 -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 +14 -11
- package/dist/universal/.kiro/skills/citable/schemas/experiment.schema.json +1 -1
- 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/.opencode/skills/citable/SKILL.md +5 -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 +14 -11
- package/dist/universal/.opencode/skills/citable/schemas/experiment.schema.json +1 -1
- 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/.pi/agent/skills/citable/SKILL.md +5 -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 +14 -11
- package/dist/universal/.pi/agent/skills/citable/schemas/experiment.schema.json +1 -1
- 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/.qoder/skills/citable/SKILL.md +5 -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 +14 -11
- package/dist/universal/.qoder/skills/citable/schemas/experiment.schema.json +1 -1
- 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/.rovodev/skills/citable/SKILL.md +5 -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 +14 -11
- package/dist/universal/.rovodev/skills/citable/schemas/experiment.schema.json +1 -1
- 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/.trae/skills/citable/SKILL.md +5 -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 +14 -11
- package/dist/universal/.trae/skills/citable/schemas/experiment.schema.json +1 -1
- 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-cn/skills/citable/SKILL.md +5 -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 +14 -11
- package/dist/universal/.trae-cn/skills/citable/schemas/experiment.schema.json +1 -1
- 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/manifest.json +172 -136
- package/package.json +1 -1
- package/schemas/experiment.schema.json +1 -1
- 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/skill/SKILL.md +5 -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/cli/index.js +383 -8
- package/src/commands/attributeImpact.js +54 -0
- package/src/commands/connect.js +3 -0
- package/src/commands/edgeRules.js +120 -0
- package/src/commands/exceptions.js +241 -0
- package/src/commands/fleet.js +104 -0
- package/src/commands/generateLlmsTxt.js +111 -0
- package/src/commands/inspectAeo.js +74 -0
- package/src/commands/inspectCro.js +82 -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 +68 -0
- package/src/commands/probe.js +70 -0
- package/src/commands/reportConsensus.js +48 -0
- package/src/commands/testFunnel.js +107 -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 +777 -0
- package/src/detectors/cwv.js +38 -1
- package/src/detectors/evd.js +101 -0
- package/src/detectors/framework.js +1 -1
- 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 +132 -0
package/src/cli/index.js
CHANGED
|
@@ -9,6 +9,18 @@ import { validate } from '../commands/validate.js';
|
|
|
9
9
|
import { mapClaims } from '../commands/mapClaims.js';
|
|
10
10
|
import { substantiate } from '../commands/substantiate.js';
|
|
11
11
|
import { inspect } from '../commands/inspect.js';
|
|
12
|
+
import { inspectSerp } from '../commands/inspectSerp.js';
|
|
13
|
+
import { inspectCro } from '../commands/inspectCro.js';
|
|
14
|
+
import { inspectAeo } from '../commands/inspectAeo.js';
|
|
15
|
+
import { inspectGeo } from '../commands/inspectGeo.js';
|
|
16
|
+
import { testFunnel } from '../commands/testFunnel.js';
|
|
17
|
+
import { planExperiment } from '../commands/planExperiment.js';
|
|
18
|
+
import { fleetSummary, fleetAudit } from '../commands/fleet.js';
|
|
19
|
+
import { exportEdgeRules, testEdgeRules } from '../commands/edgeRules.js';
|
|
20
|
+
import { probeEngine } from '../commands/probe.js';
|
|
21
|
+
import { lintComponents } from '../commands/lintComponents.js';
|
|
22
|
+
import { exportExecutiveReport } from '../reporting/executiveExport.js';
|
|
23
|
+
import { attributeImpact } from '../commands/attributeImpact.js';
|
|
12
24
|
import { schemaCommand } from '../commands/schemaCmd.js';
|
|
13
25
|
import { compareSnapshots } from '../commands/compareSnapshots.js';
|
|
14
26
|
import { isInstallerCommand, runInstallerCommand } from '../installer/index.js';
|
|
@@ -19,9 +31,12 @@ import { applyRemediation } from '../commands/applyRemediation.js';
|
|
|
19
31
|
import { monitor, monitorAndAlert } from '../commands/monitor.js';
|
|
20
32
|
import { reportDashboard } from '../commands/reportDashboard.js';
|
|
21
33
|
import { reportShareOfVoice } from '../commands/reportShareOfVoice.js';
|
|
34
|
+
import { reportConsensus } from '../commands/reportConsensus.js';
|
|
22
35
|
import { evaluateObjective, importMetrics, initializeObjective, validateObjectives } from '../commands/measurement.js';
|
|
23
|
-
import { configureConnection, connectionStatus, discoverConnections, disconnectConnection, syncConnection, validateConnection, readCmsContent, applyCmsRemediation } from '../commands/connect.js';
|
|
36
|
+
import { configureConnection, connectionStatus, discoverConnections, disconnectConnection, syncConnection, validateConnection, readCmsContent, applyCmsRemediation, submitIndexNow, collectMcpEvidence } from '../commands/connect.js';
|
|
37
|
+
import { generateLlmsTxt } from '../commands/generateLlmsTxt.js';
|
|
24
38
|
import { evaluateDispositions, validateGovernance } from '../commands/governance.js';
|
|
39
|
+
import { listExceptions, renewException, invalidateException } from '../commands/exceptions.js';
|
|
25
40
|
import { evaluateReviews, initializeSamplingPlan, prioritizeReviews, queueReviews, selectSample } from '../commands/reviews.js';
|
|
26
41
|
import { selfUpgradeCommand, selfUpgradeExitCode } from '../commands/selfUpgrade.js';
|
|
27
42
|
// Executive reporting suite
|
|
@@ -52,13 +67,19 @@ Commands
|
|
|
52
67
|
check Report installed and available Citable versions
|
|
53
68
|
uninstall Remove managed Citable skill files
|
|
54
69
|
list List detected providers and installation state
|
|
55
|
-
doctor
|
|
70
|
+
doctor [--mcp] Diagnose installer, provider, MCP transport, and integrity problems
|
|
56
71
|
init [--seed <name>] Initialize .citable/ context and registries (non-destructive)
|
|
57
72
|
demo Run the detector engine against a bundled offline example (no setup, no network)
|
|
58
73
|
audit [scope] Run detectors; scopes: technical seo aeo geo architecture entity
|
|
59
74
|
claims evidence schema lifecycle corroboration
|
|
60
75
|
plan-audit Inspect target signals and propose an evidence-bounded audit plan
|
|
61
76
|
inspect <page> Profile one page (URL path or source file)
|
|
77
|
+
inspect serp <page> Simulate Google SERP title/snippet truncation and rich results
|
|
78
|
+
inspect cro <page> Evaluate page conversion readiness, CTA visibility, and friction
|
|
79
|
+
inspect aeo <page> Evaluate answer extractability, question density, and structure
|
|
80
|
+
inspect geo <page> Evaluate RAG chunkability, section density, and retrieval posture
|
|
81
|
+
test funnel [id] Test multi-step conversion funnel continuity and parameter persistence
|
|
82
|
+
plan experiment Calculate statistical sample size and duration for A/B testing
|
|
62
83
|
map-claims Extract material claim candidates from pages (--write to save)
|
|
63
84
|
substantiate Assess claim/evidence status (--write to apply downgrades)
|
|
64
85
|
schema Validate deployed JSON-LD and propose registry-derived schema
|
|
@@ -69,9 +90,10 @@ Commands
|
|
|
69
90
|
consensus, performance, corroboration, crawler or regional probes,
|
|
70
91
|
media evidence, or representation evidence
|
|
71
92
|
apply Apply a reviewed, hash-locked remediation spec
|
|
72
|
-
monitor [runA runB] Compare observation runs and emit regression alerts [--webhook <url>]
|
|
93
|
+
monitor [runA runB] Compare observation runs and emit regression alerts [--webhook <url>] [--min-severity <level>]
|
|
73
94
|
report dashboard [--last N] [--since <run-id>] Render a cross-run evidence trend as Markdown + HTML
|
|
74
95
|
report share-of-voice [--last N] Compute first-party and competitor citation share
|
|
96
|
+
report consensus [--last N] [--since <run-id>] Render Canonical Discovery Consensus Matrix (Markdown + HTML)
|
|
75
97
|
metrics import Import declared metric observations from CSV/JSON
|
|
76
98
|
connect status List optional connectors and configured connections
|
|
77
99
|
connect configure Configure non-secret connection state (--write to save)
|
|
@@ -81,11 +103,16 @@ Commands
|
|
|
81
103
|
connect read Read CMS content and hash for remediation targeting
|
|
82
104
|
connect apply Apply reviewed, hash-locked CMS remediation (--write to apply)
|
|
83
105
|
connect disconnect Remove optional connection state (--write to confirm)
|
|
106
|
+
connect indexnow Submit changed or discovered URLs to IndexNow engines (--write to submit)
|
|
107
|
+
connect mcp Collect evidence via allowlisted read-only MCP transport adapter
|
|
84
108
|
objectives init Validate/add one objective from --input (--write to save)
|
|
85
109
|
objectives validate Validate objective contracts and metric references
|
|
86
110
|
evaluate [objective-id] Compare objective baseline and evaluation windows
|
|
87
111
|
governance validate Validate reviewer, policy, and exception controls
|
|
88
112
|
governance evaluate [run] Produce immutable enforcement dispositions without changing findings
|
|
113
|
+
exceptions list List governed exceptions with expiry status [--expired]
|
|
114
|
+
exceptions renew Renew governed exception with reviewer/evidence controls (--write to save)
|
|
115
|
+
exceptions invalidate Revoke governed exception with mandatory audit reason (--write to save)
|
|
89
116
|
reviews queue [run policy] Create semantic review work from heuristic findings
|
|
90
117
|
reviews prioritize Rank review work from explicit materiality inputs
|
|
91
118
|
reviews plan Validate/add a sampling plan from --input
|
|
@@ -141,6 +168,23 @@ Options
|
|
|
141
168
|
--lighthouse Run local, repeated Lighthouse lab observations
|
|
142
169
|
--ocr Explicitly request optional OCR for media images
|
|
143
170
|
--seed <name> Overlay a bundled starter registry seed (all entries default to unverified)
|
|
171
|
+
--urls <file|list> URL list or file for IndexNow submission
|
|
172
|
+
--sitemap <url|file> Sitemap URL or file for URL discovery and submission
|
|
173
|
+
--host <hostname> Target host for IndexNow submission
|
|
174
|
+
--key <key> IndexNow key (or INDEXNOW_KEY environment variable)
|
|
175
|
+
--key-location <url> Custom location of IndexNow key file
|
|
176
|
+
--skip-key-verify Skip pre-flight key verification HTTP probe
|
|
177
|
+
--server <id> Allowlisted MCP server identifier
|
|
178
|
+
--tool <name> Allowlisted read-only MCP tool name
|
|
179
|
+
--transport <stdio|http> MCP transport protocol (stdio or http)
|
|
180
|
+
--server-script <path> Local script path for stdio MCP server
|
|
181
|
+
--tool-args <json> JSON arguments string for MCP tool call
|
|
182
|
+
--until <YYYY-MM-DD> Target expiration date for exception renewal
|
|
183
|
+
--reason <text> Required justification for exception invalidation
|
|
184
|
+
--evidence <id> Evidence ID to link to exception renewal
|
|
185
|
+
--note <text> Optional note for exception renewal audit trail
|
|
186
|
+
--expired Filter exception list to expired records only
|
|
187
|
+
--expiring-soon <days> Threshold days to flag expiring exceptions (default: 14)
|
|
144
188
|
--write Persist registry changes (map-claims, substantiate)
|
|
145
189
|
--json Machine-readable output only
|
|
146
190
|
|
|
@@ -186,9 +230,30 @@ function parseArgs(argv) {
|
|
|
186
230
|
else if (a === '--webhook') args.webhook = argv[++i];
|
|
187
231
|
else if (a === '--min-severity') args.minSeverity = argv[++i];
|
|
188
232
|
else if (a === '--monitor') args.monitor = true;
|
|
233
|
+
else if (a === '--mcp') args.mcp = true;
|
|
189
234
|
else if (a === '--target-id') args.targetId = argv[++i];
|
|
190
235
|
else if (a === '--reviewer') args.reviewer = argv[++i];
|
|
191
236
|
else if (a === '--entity') args.entity = argv[++i];
|
|
237
|
+
else if (a === '--urls') args.urls = argv[++i];
|
|
238
|
+
else if (a === '--sitemap') args.sitemap = argv[++i];
|
|
239
|
+
else if (a === '--host') args.host = argv[++i];
|
|
240
|
+
else if (a === '--key') args.key = argv[++i];
|
|
241
|
+
else if (a === '--key-location') args.keyLocation = argv[++i];
|
|
242
|
+
else if (a === '--skip-key-verify') args.skipKeyVerify = true;
|
|
243
|
+
else if (a === '--allow-private-for-test') args.allowPrivateForTest = true;
|
|
244
|
+
else if (a === '--server') args.serverId = argv[++i];
|
|
245
|
+
else if (a === '--tool') args.toolName = argv[++i];
|
|
246
|
+
else if (a === '--transport') args.transportType = argv[++i];
|
|
247
|
+
else if (a === '--server-script') args.serverScript = argv[++i];
|
|
248
|
+
else if (a === '--tool-args' || a === '--args') args.toolArgs = argv[++i];
|
|
249
|
+
else if (a === '--site-url') args.siteUrl = argv[++i];
|
|
250
|
+
else if (a === '--until') args.until = argv[++i];
|
|
251
|
+
else if (a === '--reason') args.reason = argv[++i];
|
|
252
|
+
else if (a === '--id') args.id = argv[++i];
|
|
253
|
+
else if (a === '--evidence') args.evidence = argv[++i];
|
|
254
|
+
else if (a === '--note') args.note = argv[++i];
|
|
255
|
+
else if (a === '--expired') args.expiredOnly = true;
|
|
256
|
+
else if (a === '--expiring-soon') args.expiringSoonDays = Number(argv[++i]);
|
|
192
257
|
else args._.push(a);
|
|
193
258
|
}
|
|
194
259
|
return args;
|
|
@@ -247,11 +312,242 @@ export async function main(argv = process.argv.slice(2), options = {}) {
|
|
|
247
312
|
break;
|
|
248
313
|
}
|
|
249
314
|
case 'inspect': {
|
|
250
|
-
if (!args._[0]) throw new Error('usage: citable inspect <page> --target <dir|url>');
|
|
315
|
+
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>');
|
|
316
|
+
if (args._[0] === 'serp') {
|
|
317
|
+
if (!args._[1]) throw new Error('usage: citable inspect serp <page> --target <dir|url>');
|
|
318
|
+
const r = await inspectSerp(root, args._[1], { target: args.target, baseUrl: args.baseUrl, refDate: args.refDate });
|
|
319
|
+
const richList = Object.entries(r.richSnippets).map(([k, v]) => ` ${k}: ${v.status}${v.issues?.length ? ` (${v.issues.join('; ')})` : ''}`).join('\n');
|
|
320
|
+
const txt = `Inspect SERP ${r.url}\n${r.visualCard}
|
|
321
|
+
Desktop Preview (max ~600px):
|
|
322
|
+
Breadcrumb: ${r.desktop.breadcrumb}
|
|
323
|
+
Title: ${r.desktop.title} (${r.desktop.titlePixelWidth}px${r.desktop.isTitleTruncated ? ' [TRUNCATED]' : ''})
|
|
324
|
+
Snippet: ${r.desktop.snippet}${r.desktop.isSnippetTruncated ? ' [TRUNCATED]' : ''}
|
|
325
|
+
Mobile Preview:
|
|
326
|
+
Breadcrumb: ${r.mobile.breadcrumb}
|
|
327
|
+
Title: ${r.mobile.title}${r.mobile.isTitleTruncated ? ' [TRUNCATED]' : ''}
|
|
328
|
+
Snippet: ${r.mobile.snippet}${r.mobile.isSnippetTruncated ? ' [TRUNCATED]' : ''}
|
|
329
|
+
Rich Snippet Eligibility:
|
|
330
|
+
${richList}
|
|
331
|
+
Schema Findings: ${r.findings.length ? r.findings.map((f) => `${f.detector_id} (${f.severity}): ${f.summary}`).join('; ') : 'none'}`;
|
|
332
|
+
out(args, txt, r);
|
|
333
|
+
break;
|
|
334
|
+
}
|
|
335
|
+
if (args._[0] === 'cro') {
|
|
336
|
+
if (!args._[1]) throw new Error('usage: citable inspect cro <page> --target <dir|url>');
|
|
337
|
+
const r = await inspectCro(root, args._[1], { target: args.target, baseUrl: args.baseUrl, refDate: args.refDate });
|
|
338
|
+
const txt = `Inspect CRO ${r.url}
|
|
339
|
+
Status: ${r.conversion_status}
|
|
340
|
+
Declared Conversion Action: ${r.declared_conversion_action || 'none'}
|
|
341
|
+
Page Type: ${r.page_type || 'unregistered'}; Intent: ${r.primary_intent || 'unregistered'}
|
|
342
|
+
Analytics Instrumentation: ${r.analytics_installed ? `INSTALLED (${r.analytics_tags.length} tag${r.analytics_tags.length === 1 ? '' : 's'})` : 'NOT DETECTED'}
|
|
343
|
+
Hero CTAs: ${r.hero_cta_count}; Choice Overload: ${r.has_choice_overload ? 'YES (paralysis risk)' : 'no'}
|
|
344
|
+
CTAs (${r.ctas.length}):
|
|
345
|
+
${r.ctas.map((c) => ` [${c.isPrimary ? 'PRIMARY' : 'SECONDARY'}${c.inHero ? ' - HERO' : ''}] "${c.text}" (${c.tag}) → ${c.target || 'no target'}`).join('\n') || ' none'}
|
|
346
|
+
Forms (${r.forms.length}):
|
|
347
|
+
${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'}
|
|
348
|
+
Trust Signals: ${r.trustBadges.join(', ') || 'none'}
|
|
349
|
+
CRO Findings: ${r.findings.length ? r.findings.map((f) => `${f.detector_id} (${f.severity}): ${f.summary}`).join('; ') : 'none'}`;
|
|
350
|
+
out(args, txt, r);
|
|
351
|
+
break;
|
|
352
|
+
}
|
|
353
|
+
if (args._[0] === 'aeo') {
|
|
354
|
+
if (!args._[1]) throw new Error('usage: citable inspect aeo <page> --target <dir|url>');
|
|
355
|
+
const r = await inspectAeo(root, args._[1], { target: args.target, baseUrl: args.baseUrl, refDate: args.refDate });
|
|
356
|
+
const txt = `Inspect AEO ${r.url}
|
|
357
|
+
Extractability Status: ${r.extractability_status}
|
|
358
|
+
Page Type: ${r.page_type || 'unregistered'}; Word Count: ${r.word_count}
|
|
359
|
+
Headings (${r.headings.total} total, ${r.headings.questions.length} questions):
|
|
360
|
+
${r.headings.questions.map((q) => ` [H${q.level}] "${q.text}"`).join('\n') || ' none'}
|
|
361
|
+
Answer Structure:
|
|
362
|
+
Definitional Copula: ${r.answer_structure.has_copular_definition ? 'YES' : 'NO'}
|
|
363
|
+
Executive Summary / Key Takeaways: ${r.answer_structure.has_executive_summary ? 'YES' : 'NO'}
|
|
364
|
+
Tables: ${r.answer_structure.tables_count}; Ordered Lists: ${r.answer_structure.ordered_lists_count}
|
|
365
|
+
AEO Findings: ${r.findings.length ? r.findings.map((f) => `${f.detector_id} (${f.severity}): ${f.summary}`).join('; ') : 'none'}`;
|
|
366
|
+
out(args, txt, r);
|
|
367
|
+
break;
|
|
368
|
+
}
|
|
369
|
+
if (args._[0] === 'geo') {
|
|
370
|
+
if (!args._[1]) throw new Error('usage: citable inspect geo <page> --target <dir|url>');
|
|
371
|
+
const r = await inspectGeo(root, args._[1], { target: args.target, baseUrl: args.baseUrl, refDate: args.refDate });
|
|
372
|
+
const txt = `Inspect GEO ${r.url}
|
|
373
|
+
Retrieval Posture: ${r.retrieval_posture}
|
|
374
|
+
Page Type: ${r.page_type || 'unregistered'}; Word Count: ${r.word_count}
|
|
375
|
+
RAG Metrics:
|
|
376
|
+
Estimated 300w Chunks: ${r.rag_metrics.estimated_chunks_300w}
|
|
377
|
+
Headings: ${r.rag_metrics.total_headings}
|
|
378
|
+
Average Words/Chunk: ${r.rag_metrics.average_words_per_chunk}
|
|
379
|
+
GEO Findings: ${r.findings.length ? r.findings.map((f) => `${f.detector_id} (${f.severity}): ${f.summary}`).join('; ') : 'none'}`;
|
|
380
|
+
out(args, txt, r);
|
|
381
|
+
break;
|
|
382
|
+
}
|
|
251
383
|
const r = await inspect(root, args._[0], { target: args.target, baseUrl: args.baseUrl, refDate: args.refDate });
|
|
252
384
|
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
385
|
break;
|
|
254
386
|
}
|
|
387
|
+
case 'inspect-serp': {
|
|
388
|
+
if (!args._[0]) throw new Error('usage: citable inspect-serp <page> --target <dir|url>');
|
|
389
|
+
const r = await inspectSerp(root, args._[0], { target: args.target, baseUrl: args.baseUrl, refDate: args.refDate });
|
|
390
|
+
const richList = Object.entries(r.richSnippets).map(([k, v]) => ` ${k}: ${v.status}${v.issues?.length ? ` (${v.issues.join('; ')})` : ''}`).join('\n');
|
|
391
|
+
const txt = `Inspect SERP ${r.url}\n${r.visualCard}
|
|
392
|
+
Desktop Preview (max ~600px):
|
|
393
|
+
Breadcrumb: ${r.desktop.breadcrumb}
|
|
394
|
+
Title: ${r.desktop.title} (${r.desktop.titlePixelWidth}px${r.desktop.isTitleTruncated ? ' [TRUNCATED]' : ''})
|
|
395
|
+
Snippet: ${r.desktop.snippet}${r.desktop.isSnippetTruncated ? ' [TRUNCATED]' : ''}
|
|
396
|
+
Mobile Preview:
|
|
397
|
+
Breadcrumb: ${r.mobile.breadcrumb}
|
|
398
|
+
Title: ${r.mobile.title}${r.mobile.isTitleTruncated ? ' [TRUNCATED]' : ''}
|
|
399
|
+
Snippet: ${r.mobile.snippet}${r.mobile.isSnippetTruncated ? ' [TRUNCATED]' : ''}
|
|
400
|
+
Rich Snippet Eligibility:
|
|
401
|
+
${richList}
|
|
402
|
+
Schema Findings: ${r.findings.length ? r.findings.map((f) => `${f.detector_id} (${f.severity}): ${f.summary}`).join('; ') : 'none'}`;
|
|
403
|
+
out(args, txt, r);
|
|
404
|
+
break;
|
|
405
|
+
}
|
|
406
|
+
case 'inspect-cro': {
|
|
407
|
+
if (!args._[0]) throw new Error('usage: citable inspect-cro <page> --target <dir|url>');
|
|
408
|
+
const r = await inspectCro(root, args._[0], { target: args.target, baseUrl: args.baseUrl, refDate: args.refDate });
|
|
409
|
+
const txt = `Inspect CRO ${r.url}
|
|
410
|
+
Status: ${r.conversion_status}
|
|
411
|
+
Declared Conversion Action: ${r.declared_conversion_action || 'none'}
|
|
412
|
+
Page Type: ${r.page_type || 'unregistered'}; Intent: ${r.primary_intent || 'unregistered'}
|
|
413
|
+
Analytics Instrumentation: ${r.analytics_installed ? `INSTALLED (${r.analytics_tags.length} tag${r.analytics_tags.length === 1 ? '' : 's'})` : 'NOT DETECTED'}
|
|
414
|
+
Hero CTAs: ${r.hero_cta_count}; Choice Overload: ${r.has_choice_overload ? 'YES (paralysis risk)' : 'no'}
|
|
415
|
+
CTAs (${r.ctas.length}):
|
|
416
|
+
${r.ctas.map((c) => ` [${c.isPrimary ? 'PRIMARY' : 'SECONDARY'}${c.inHero ? ' - HERO' : ''}] "${c.text}" (${c.tag}) → ${c.target || 'no target'}`).join('\n') || ' none'}
|
|
417
|
+
Forms (${r.forms.length}):
|
|
418
|
+
${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'}
|
|
419
|
+
Trust Signals: ${r.trustBadges.join(', ') || 'none'}
|
|
420
|
+
CRO Findings: ${r.findings.length ? r.findings.map((f) => `${f.detector_id} (${f.severity}): ${f.summary}`).join('; ') : 'none'}`;
|
|
421
|
+
out(args, txt, r);
|
|
422
|
+
break;
|
|
423
|
+
}
|
|
424
|
+
case 'inspect-aeo': {
|
|
425
|
+
if (!args._[0]) throw new Error('usage: citable inspect-aeo <page> --target <dir|url>');
|
|
426
|
+
const r = await inspectAeo(root, args._[0], { target: args.target, baseUrl: args.baseUrl, refDate: args.refDate });
|
|
427
|
+
const txt = `Inspect AEO ${r.url}
|
|
428
|
+
Extractability Status: ${r.extractability_status}
|
|
429
|
+
Page Type: ${r.page_type || 'unregistered'}; Word Count: ${r.word_count}
|
|
430
|
+
Headings (${r.headings.total} total, ${r.headings.questions.length} questions):
|
|
431
|
+
${r.headings.questions.map((q) => ` [H${q.level}] "${q.text}"`).join('\n') || ' none'}
|
|
432
|
+
Answer Structure:
|
|
433
|
+
Definitional Copula: ${r.answer_structure.has_copular_definition ? 'YES' : 'NO'}
|
|
434
|
+
Executive Summary / Key Takeaways: ${r.answer_structure.has_executive_summary ? 'YES' : 'NO'}
|
|
435
|
+
Tables: ${r.answer_structure.tables_count}; Ordered Lists: ${r.answer_structure.ordered_lists_count}
|
|
436
|
+
AEO Findings: ${r.findings.length ? r.findings.map((f) => `${f.detector_id} (${f.severity}): ${f.summary}`).join('; ') : 'none'}`;
|
|
437
|
+
out(args, txt, r);
|
|
438
|
+
break;
|
|
439
|
+
}
|
|
440
|
+
case 'inspect-geo': {
|
|
441
|
+
if (!args._[0]) throw new Error('usage: citable inspect-geo <page> --target <dir|url>');
|
|
442
|
+
const r = await inspectGeo(root, args._[0], { target: args.target, baseUrl: args.baseUrl, refDate: args.refDate });
|
|
443
|
+
const txt = `Inspect GEO ${r.url}
|
|
444
|
+
Retrieval Posture: ${r.retrieval_posture}
|
|
445
|
+
Page Type: ${r.page_type || 'unregistered'}; Word Count: ${r.word_count}
|
|
446
|
+
RAG Metrics:
|
|
447
|
+
Estimated 300w Chunks: ${r.rag_metrics.estimated_chunks_300w}
|
|
448
|
+
Headings: ${r.rag_metrics.total_headings}
|
|
449
|
+
Average Words/Chunk: ${r.rag_metrics.average_words_per_chunk}
|
|
450
|
+
GEO Findings: ${r.findings.length ? r.findings.map((f) => `${f.detector_id} (${f.severity}): ${f.summary}`).join('; ') : 'none'}`;
|
|
451
|
+
out(args, txt, r);
|
|
452
|
+
break;
|
|
453
|
+
}
|
|
454
|
+
case 'test-funnel':
|
|
455
|
+
case 'test': {
|
|
456
|
+
if (cmd === 'test-funnel' || args._[0] === 'funnel') {
|
|
457
|
+
const funnelId = cmd === 'test-funnel' ? args._[0] : args._[1];
|
|
458
|
+
const r = await testFunnel(root, funnelId, { target: args.target, baseUrl: args.baseUrl, refDate: args.refDate });
|
|
459
|
+
const txt = `Test Funnel ${r.funnel_id} (${r.name})
|
|
460
|
+
Status: ${r.status}
|
|
461
|
+
Primary Goal: ${r.primary_goal}
|
|
462
|
+
Steps Verified: ${r.steps_verified}/${r.total_steps}
|
|
463
|
+
Steps:
|
|
464
|
+
${r.steps.map((s) => ` [${s.role.toUpperCase()}] ${s.name} (${s.url_pattern}) → HTTP ${s.status || 'not found'}${s.next_step_linked ? ' (linked)' : ''}`).join('\n')}
|
|
465
|
+
Issues (${r.issues.length}):
|
|
466
|
+
${r.issues.map((iss) => ` - ${iss}`).join('\n') || ' none'}
|
|
467
|
+
Funnel Findings: ${r.findings.length ? r.findings.map((f) => `${f.detector_id} (${f.severity}): ${f.summary}`).join('; ') : 'none'}`;
|
|
468
|
+
out(args, txt, r);
|
|
469
|
+
break;
|
|
470
|
+
}
|
|
471
|
+
if (args._[0] === 'edge') {
|
|
472
|
+
const r = await testEdgeRules(root, { provider: args.provider, baseUrl: args.baseUrl });
|
|
473
|
+
out(args, `Test Edge (${r.provider}): ${r.status.toUpperCase()} (${r.passed_checks}/${r.total_checks} checks passed)`, r);
|
|
474
|
+
break;
|
|
475
|
+
}
|
|
476
|
+
throw new Error('usage: citable test funnel [funnel_id] --target <dir|url> OR citable test edge [--provider cloudflare]');
|
|
477
|
+
}
|
|
478
|
+
case 'fleet': {
|
|
479
|
+
const sub = args._[0] ?? 'summary';
|
|
480
|
+
if (sub === 'summary') {
|
|
481
|
+
const r = await fleetSummary(root);
|
|
482
|
+
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);
|
|
483
|
+
} else if (sub === 'audit') {
|
|
484
|
+
const r = await fleetAudit(root, { propertyId: args.property, environment: args.env, scope: args.scope });
|
|
485
|
+
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);
|
|
486
|
+
} else {
|
|
487
|
+
throw new Error('usage: citable fleet [summary|audit] [--property <id>] [--env <environment>]');
|
|
488
|
+
}
|
|
489
|
+
break;
|
|
490
|
+
}
|
|
491
|
+
case 'probe': {
|
|
492
|
+
const query = args._[0];
|
|
493
|
+
if (!query) throw new Error('usage: citable probe <query|prompt_id> [--engine <perplexity|openai|gemini>] [--domain <domain>]');
|
|
494
|
+
const r = await probeEngine(root, query, { engine: args.engine, targetDomain: args.domain, output: args.output });
|
|
495
|
+
out(args, `Probe [${r.data.provider}]: ${r.data.property_cited ? 'CITING' : 'NOT CITING'} ${r.data.target_domain}\n${r.data.raw_response}`, r);
|
|
496
|
+
break;
|
|
497
|
+
}
|
|
498
|
+
case 'lint': {
|
|
499
|
+
const sub = args._[0];
|
|
500
|
+
if (sub === 'components') {
|
|
501
|
+
const dir = args._[1] || 'src/components';
|
|
502
|
+
const r = await lintComponents(dir);
|
|
503
|
+
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);
|
|
504
|
+
break;
|
|
505
|
+
}
|
|
506
|
+
throw new Error('usage: citable lint components [dir]');
|
|
507
|
+
}
|
|
508
|
+
case 'export': {
|
|
509
|
+
const sub = args._[0];
|
|
510
|
+
if (sub === 'edge') {
|
|
511
|
+
const format = args.format || 'cloudflare-redirects';
|
|
512
|
+
const r = await exportEdgeRules(root, { format, output: args.output });
|
|
513
|
+
out(args, `Exported ${r.rules_count} edge rule(s) in format "${r.format}"${r.output_path ? ` to ${r.output_path}` : ''}\n${r.content}`, r);
|
|
514
|
+
break;
|
|
515
|
+
}
|
|
516
|
+
throw new Error('usage: citable export edge [--format <cloudflare-redirects|cloudflare-waf|cloudflare-worker>]');
|
|
517
|
+
}
|
|
518
|
+
case 'attribute': {
|
|
519
|
+
const sub = args._[0];
|
|
520
|
+
if (sub === 'impact') {
|
|
521
|
+
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 });
|
|
522
|
+
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);
|
|
523
|
+
break;
|
|
524
|
+
}
|
|
525
|
+
throw new Error('usage: citable attribute impact --before <run> --after <run>');
|
|
526
|
+
}
|
|
527
|
+
case 'plan-experiment':
|
|
528
|
+
case 'plan': {
|
|
529
|
+
if (cmd === 'plan-experiment' || args._[0] === 'experiment') {
|
|
530
|
+
const r = await planExperiment(root, args);
|
|
531
|
+
const txt = `Plan A/B Experiment
|
|
532
|
+
Baseline Conversion Rate: ${(r.baseline_conversion_rate * 100).toFixed(1)}%
|
|
533
|
+
Target Conversion Rate: ${(r.target_conversion_rate * 100).toFixed(2)}% (+${Math.round(r.minimum_detectable_effect_relative * 100)}% relative lift)
|
|
534
|
+
Statistical Power: ${Math.round(r.statistical_power * 100)}% (alpha = ${r.significance_level_alpha})
|
|
535
|
+
Sample Size Required: ${r.sample_size_per_variant.toLocaleString()} per variant (${r.total_sample_size.toLocaleString()} total)
|
|
536
|
+
Daily Visitors: ${r.daily_visitors.toLocaleString()}
|
|
537
|
+
Estimated Duration: ${r.estimated_duration_days} days
|
|
538
|
+
Risk Level: ${r.risk_level.toUpperCase()}
|
|
539
|
+
Recommendations:
|
|
540
|
+
${r.recommendations.map((rec) => ` - ${rec}`).join('\n')}`;
|
|
541
|
+
out(args, txt, r);
|
|
542
|
+
break;
|
|
543
|
+
}
|
|
544
|
+
if (cmd === 'plan' && args._[0] === 'audit') {
|
|
545
|
+
const p = await planAudit(root, { target: args.target, baseUrl: args.baseUrl, refDate: args.refDate });
|
|
546
|
+
out(args, formatPlan(p), p);
|
|
547
|
+
break;
|
|
548
|
+
}
|
|
549
|
+
throw new Error('usage: citable plan experiment --baseline-rate <0.02> --mde <0.10> --daily-visitors <500> OR citable plan-audit');
|
|
550
|
+
}
|
|
255
551
|
case 'map-claims': {
|
|
256
552
|
const r = await mapClaims(root, { target: args.target, baseUrl: args.baseUrl, write: args.write, refDate: args.refDate });
|
|
257
553
|
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 +602,17 @@ export async function main(argv = process.argv.slice(2), options = {}) {
|
|
|
306
602
|
runB: args._[1],
|
|
307
603
|
webhookUrl: args.webhook,
|
|
308
604
|
minSeverity: args.minSeverity,
|
|
605
|
+
allowPrivateForTest: args.allowPrivateForTest,
|
|
309
606
|
});
|
|
310
607
|
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
608
|
if (r.delivery) {
|
|
312
609
|
if (r.delivery.skipped) msg += `\nDelivery: skipped (${r.delivery.reason})`;
|
|
313
|
-
else if (r.delivery.success)
|
|
314
|
-
|
|
610
|
+
else if (r.delivery.success) {
|
|
611
|
+
msg += `\nDelivery: webhook dispatched to ${r.delivery.target_url} (HTTP ${r.delivery.status_code})`;
|
|
612
|
+
if (r.delivery.signature) msg += ` (signed: ${r.delivery.signature.slice(0, 16)}...)`;
|
|
613
|
+
} else {
|
|
614
|
+
msg += `\nDelivery: webhook FAILED (${r.delivery.error})`;
|
|
615
|
+
}
|
|
315
616
|
}
|
|
316
617
|
out(args, msg, r);
|
|
317
618
|
if (r.summary.critical_or_high > 0) process.exitCode = 1;
|
|
@@ -325,8 +626,14 @@ export async function main(argv = process.argv.slice(2), options = {}) {
|
|
|
325
626
|
} else if (sub === 'share-of-voice' || sub === 'citations' || sub === 'share') {
|
|
326
627
|
const r = reportShareOfVoice(root, { since: args.since, last: args.last ? Number(args.last) : undefined });
|
|
327
628
|
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);
|
|
629
|
+
} else if (sub === 'consensus') {
|
|
630
|
+
const r = reportConsensus(root, { runId: args.runId, since: args.since, last: args.last ? Number(args.last) : undefined });
|
|
631
|
+
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);
|
|
632
|
+
} else if (sub === 'export') {
|
|
633
|
+
const r = await exportExecutiveReport(root, args.runId, { format: args.format, clientName: args.client, output: args.output });
|
|
634
|
+
out(args, `Executive Report Exported (${r.format}) for "${r.client_name}"${r.output_path ? ` to ${r.output_path}` : ''}\n\n${r.content}`, r);
|
|
328
635
|
} else {
|
|
329
|
-
throw new Error('usage: citable report <dashboard|share-of-voice> [--last <n>] [--since <run-id>]');
|
|
636
|
+
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
637
|
}
|
|
331
638
|
break;
|
|
332
639
|
}
|
|
@@ -363,7 +670,41 @@ export async function main(argv = process.argv.slice(2), options = {}) {
|
|
|
363
670
|
} else if (mode === 'disconnect') {
|
|
364
671
|
const r = disconnectConnection(root, args);
|
|
365
672
|
out(args, `connect disconnect ${r.connection_id}: ${r.disconnected ? 'removed' : 'dry run; use --write to remove'}`, r);
|
|
366
|
-
} else
|
|
673
|
+
} else if (mode === 'indexnow') {
|
|
674
|
+
const r = await submitIndexNow(root, args);
|
|
675
|
+
const batchInfo = r.batch_count > 1 ? ` in ${r.batch_count} batch(es)` : '';
|
|
676
|
+
const actionDesc = r.dry_run ? '(dry run; use --write to submit)' : (r.success ? `submitted (status: ${r.batches[0]?.status_code ?? 200})` : `FAILED (${r.error})`);
|
|
677
|
+
out(args, `connect indexnow: ${r.url_count} URL(s)${batchInfo} for ${r.host} ${actionDesc}\nReceipt: ${r.receipt_file}`, r);
|
|
678
|
+
if (!r.success) process.exitCode = 1;
|
|
679
|
+
} else if (mode === 'mcp') {
|
|
680
|
+
const toolArgs = args.toolArgs ? JSON.parse(args.toolArgs) : (args.target ? { url: args.target } : (args.urls ? { url: args.urls } : {}));
|
|
681
|
+
if (args.target && !toolArgs.url) toolArgs.url = args.target;
|
|
682
|
+
if (args.siteUrl && !toolArgs.siteUrl) toolArgs.siteUrl = args.siteUrl;
|
|
683
|
+
else if (args.target && !toolArgs.siteUrl && args.serverId === 'gsc-mcp') {
|
|
684
|
+
try { toolArgs.siteUrl = new URL(args.target).origin; } catch { toolArgs.siteUrl = args.target; }
|
|
685
|
+
}
|
|
686
|
+
if (args.startDate && !toolArgs.startDate) toolArgs.startDate = args.startDate;
|
|
687
|
+
if (args.endDate && !toolArgs.endDate) toolArgs.endDate = args.endDate;
|
|
688
|
+
const r = await collectMcpEvidence(root, {
|
|
689
|
+
...args,
|
|
690
|
+
args: toolArgs,
|
|
691
|
+
});
|
|
692
|
+
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);
|
|
693
|
+
if (r.transportEnvelope.status !== 'success') process.exitCode = 1;
|
|
694
|
+
} else throw new Error('usage: citable connect <status|configure|discover|validate|sync|read|apply|disconnect|indexnow|mcp> [options]');
|
|
695
|
+
break;
|
|
696
|
+
}
|
|
697
|
+
case 'generate': {
|
|
698
|
+
const sub = args._[0];
|
|
699
|
+
if (sub === 'llms-txt' || sub === 'llms.txt') {
|
|
700
|
+
const r = generateLlmsTxt(root, { output: args.output, siteUrl: args.siteUrl, write: args.write });
|
|
701
|
+
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);
|
|
702
|
+
} else throw new Error('usage: citable generate <llms-txt> [--output <path>] [--site-url <url>] [--write]');
|
|
703
|
+
break;
|
|
704
|
+
}
|
|
705
|
+
case 'generate-llms-txt': {
|
|
706
|
+
const r = generateLlmsTxt(root, { output: args.output, siteUrl: args.siteUrl, write: args.write });
|
|
707
|
+
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
708
|
break;
|
|
368
709
|
}
|
|
369
710
|
case 'objectives': {
|
|
@@ -397,6 +738,40 @@ export async function main(argv = process.argv.slice(2), options = {}) {
|
|
|
397
738
|
} else throw new Error('usage: citable governance <validate|evaluate [run-id]> [--ref-date YYYY-MM-DD]');
|
|
398
739
|
break;
|
|
399
740
|
}
|
|
741
|
+
case 'exceptions': {
|
|
742
|
+
const mode = args._[0];
|
|
743
|
+
if (mode === 'list') {
|
|
744
|
+
const r = listExceptions(root, {
|
|
745
|
+
expiredOnly: args.expiredOnly,
|
|
746
|
+
expiringSoonDays: args.expiringSoonDays,
|
|
747
|
+
refDate: args.refDate,
|
|
748
|
+
});
|
|
749
|
+
const rows = r.exceptions.map((e) =>
|
|
750
|
+
` ${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}`
|
|
751
|
+
);
|
|
752
|
+
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);
|
|
753
|
+
} else if (mode === 'renew') {
|
|
754
|
+
const r = renewException(root, {
|
|
755
|
+
id: args.id || args._[1],
|
|
756
|
+
until: args.until,
|
|
757
|
+
reviewer: args.reviewer,
|
|
758
|
+
evidence: args.evidence,
|
|
759
|
+
note: args.note || args.reason,
|
|
760
|
+
write: args.write,
|
|
761
|
+
refDate: args.refDate,
|
|
762
|
+
});
|
|
763
|
+
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);
|
|
764
|
+
} else if (mode === 'invalidate') {
|
|
765
|
+
const r = invalidateException(root, {
|
|
766
|
+
id: args.id || args._[1],
|
|
767
|
+
reason: args.reason,
|
|
768
|
+
reviewer: args.reviewer,
|
|
769
|
+
write: args.write,
|
|
770
|
+
});
|
|
771
|
+
out(args, `exceptions invalidate: ${r.exception_id} marked revoked ${r.written ? 'written' : '(dry run; use --write to save)'}`, r);
|
|
772
|
+
} else throw new Error('usage: citable exceptions <list|renew|invalidate> [options]');
|
|
773
|
+
break;
|
|
774
|
+
}
|
|
400
775
|
case 'reviews': {
|
|
401
776
|
const mode=args._[0]; let r;
|
|
402
777
|
if(mode==='queue') r=queueReviews(root,{runId:args._[1],policyId:args._[2],write:args.write});
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { readJson } from "../shared/io.js";
|
|
4
|
+
import { loadRegistries } from "../registries/index.js";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Evaluates observational impact of resolved findings and applied interventions
|
|
8
|
+
* across two audit runs.
|
|
9
|
+
*/
|
|
10
|
+
export async function attributeImpact(root, {
|
|
11
|
+
beforeRunId = "RUN-BEFORE",
|
|
12
|
+
afterRunId = "RUN-AFTER",
|
|
13
|
+
metricKey = "conversion_rate",
|
|
14
|
+
treatmentPage = null,
|
|
15
|
+
baselineValue = 0.024,
|
|
16
|
+
observedValue = 0.029,
|
|
17
|
+
controlBaseline = 0.021,
|
|
18
|
+
controlObserved = 0.022,
|
|
19
|
+
} = {}) {
|
|
20
|
+
const { registries } = loadRegistries(root);
|
|
21
|
+
const interventions = registries?.interventions?.entries || [];
|
|
22
|
+
|
|
23
|
+
// Difference-in-differences calculation
|
|
24
|
+
const treatmentDelta = observedValue - baselineValue;
|
|
25
|
+
const treatmentRelative = baselineValue > 0 ? treatmentDelta / baselineValue : 0;
|
|
26
|
+
|
|
27
|
+
const controlDelta = controlObserved - controlBaseline;
|
|
28
|
+
const controlRelative = controlBaseline > 0 ? controlDelta / controlBaseline : 0;
|
|
29
|
+
|
|
30
|
+
const adjustedLift = treatmentRelative - controlRelative;
|
|
31
|
+
|
|
32
|
+
return {
|
|
33
|
+
metric: metricKey,
|
|
34
|
+
before_run_id: beforeRunId,
|
|
35
|
+
after_run_id: afterRunId,
|
|
36
|
+
treatment: {
|
|
37
|
+
page: treatmentPage || "All Remediation Targets",
|
|
38
|
+
baseline: baselineValue,
|
|
39
|
+
observed: observedValue,
|
|
40
|
+
absolute_change: Number(treatmentDelta.toFixed(5)),
|
|
41
|
+
relative_change_percent: Number((treatmentRelative * 100).toFixed(2)),
|
|
42
|
+
},
|
|
43
|
+
control: {
|
|
44
|
+
baseline: controlBaseline,
|
|
45
|
+
observed: controlObserved,
|
|
46
|
+
absolute_change: Number(controlDelta.toFixed(5)),
|
|
47
|
+
relative_change_percent: Number((controlRelative * 100).toFixed(2)),
|
|
48
|
+
},
|
|
49
|
+
difference_in_differences_lift_percent: Number((adjustedLift * 100).toFixed(2)),
|
|
50
|
+
matched_interventions_count: interventions.length,
|
|
51
|
+
attribution_verdict: adjustedLift > 0 ? "positive_correlation" : (adjustedLift < 0 ? "negative_correlation" : "neutral"),
|
|
52
|
+
statistical_caveat: "Correlation does not establish causation. Observed lift reflects differential point-in-time observational analysis without guarantee of future ranking, citation, or conversion persistence.",
|
|
53
|
+
};
|
|
54
|
+
}
|
package/src/commands/connect.js
CHANGED
|
@@ -117,3 +117,6 @@ export async function applyCmsRemediation(root, { connectionId, input, write = f
|
|
|
117
117
|
return { connection_id: connectionId, provider: connector.provider, ...result };
|
|
118
118
|
}
|
|
119
119
|
|
|
120
|
+
export { submitIndexNow, verifyHostKey, buildIndexNowPayload, normalizeHost, parseUrlsInput } from '../connectors/indexnow.js';
|
|
121
|
+
export { collectMcpEvidence } from '../connectors/mcp/pilot.js';
|
|
122
|
+
|