@nebulacomponents/citable 1.16.0 → 1.18.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 +47 -0
- package/README.md +16 -3
- package/dist/universal/.agents/skills/citable/SKILL.md +66 -1
- package/dist/universal/.agents/skills/citable/VERSION +1 -1
- package/dist/universal/.agents/skills/citable/manifest.json +9 -6
- package/dist/universal/.agents/skills/citable/schemas/cli-output-envelope.schema.json +30 -0
- package/dist/universal/.agents/skills/citable/schemas/finding.schema.json +69 -0
- package/dist/universal/.agents/skills/citable/schemas/remediation-verification.schema.json +98 -0
- package/dist/universal/.agents/skills/citable/schemas/sow.schema.json +92 -0
- package/dist/universal/.claude/agents/citable/manifest.json +2 -2
- package/dist/universal/.claude/skills/citable/SKILL.md +66 -1
- package/dist/universal/.claude/skills/citable/VERSION +1 -1
- package/dist/universal/.claude/skills/citable/manifest.json +9 -6
- package/dist/universal/.claude/skills/citable/schemas/cli-output-envelope.schema.json +30 -0
- package/dist/universal/.claude/skills/citable/schemas/finding.schema.json +69 -0
- package/dist/universal/.claude/skills/citable/schemas/remediation-verification.schema.json +98 -0
- package/dist/universal/.claude/skills/citable/schemas/sow.schema.json +92 -0
- package/dist/universal/.cursor/skills/citable/SKILL.md +66 -1
- package/dist/universal/.cursor/skills/citable/VERSION +1 -1
- package/dist/universal/.cursor/skills/citable/manifest.json +9 -6
- package/dist/universal/.cursor/skills/citable/schemas/cli-output-envelope.schema.json +30 -0
- package/dist/universal/.cursor/skills/citable/schemas/finding.schema.json +69 -0
- package/dist/universal/.cursor/skills/citable/schemas/remediation-verification.schema.json +98 -0
- package/dist/universal/.cursor/skills/citable/schemas/sow.schema.json +92 -0
- package/dist/universal/.gemini/skills/citable/SKILL.md +66 -1
- package/dist/universal/.gemini/skills/citable/VERSION +1 -1
- package/dist/universal/.gemini/skills/citable/manifest.json +9 -6
- package/dist/universal/.gemini/skills/citable/schemas/cli-output-envelope.schema.json +30 -0
- package/dist/universal/.gemini/skills/citable/schemas/finding.schema.json +69 -0
- package/dist/universal/.gemini/skills/citable/schemas/remediation-verification.schema.json +98 -0
- package/dist/universal/.gemini/skills/citable/schemas/sow.schema.json +92 -0
- package/dist/universal/.github/skills/citable/SKILL.md +66 -1
- package/dist/universal/.github/skills/citable/VERSION +1 -1
- package/dist/universal/.github/skills/citable/manifest.json +9 -6
- package/dist/universal/.github/skills/citable/schemas/cli-output-envelope.schema.json +30 -0
- package/dist/universal/.github/skills/citable/schemas/finding.schema.json +69 -0
- package/dist/universal/.github/skills/citable/schemas/remediation-verification.schema.json +98 -0
- package/dist/universal/.github/skills/citable/schemas/sow.schema.json +92 -0
- package/dist/universal/.kiro/skills/citable/SKILL.md +66 -1
- package/dist/universal/.kiro/skills/citable/VERSION +1 -1
- package/dist/universal/.kiro/skills/citable/manifest.json +9 -6
- package/dist/universal/.kiro/skills/citable/schemas/cli-output-envelope.schema.json +30 -0
- package/dist/universal/.kiro/skills/citable/schemas/finding.schema.json +69 -0
- package/dist/universal/.kiro/skills/citable/schemas/remediation-verification.schema.json +98 -0
- package/dist/universal/.kiro/skills/citable/schemas/sow.schema.json +92 -0
- package/dist/universal/.opencode/skills/citable/SKILL.md +66 -1
- package/dist/universal/.opencode/skills/citable/VERSION +1 -1
- package/dist/universal/.opencode/skills/citable/manifest.json +9 -6
- package/dist/universal/.opencode/skills/citable/schemas/cli-output-envelope.schema.json +30 -0
- package/dist/universal/.opencode/skills/citable/schemas/finding.schema.json +69 -0
- package/dist/universal/.opencode/skills/citable/schemas/remediation-verification.schema.json +98 -0
- package/dist/universal/.opencode/skills/citable/schemas/sow.schema.json +92 -0
- package/dist/universal/.pi/agent/skills/citable/SKILL.md +66 -1
- package/dist/universal/.pi/agent/skills/citable/VERSION +1 -1
- package/dist/universal/.pi/agent/skills/citable/manifest.json +9 -6
- package/dist/universal/.pi/agent/skills/citable/schemas/cli-output-envelope.schema.json +30 -0
- package/dist/universal/.pi/agent/skills/citable/schemas/finding.schema.json +69 -0
- package/dist/universal/.pi/agent/skills/citable/schemas/remediation-verification.schema.json +98 -0
- package/dist/universal/.pi/agent/skills/citable/schemas/sow.schema.json +92 -0
- package/dist/universal/.qoder/skills/citable/SKILL.md +66 -1
- package/dist/universal/.qoder/skills/citable/VERSION +1 -1
- package/dist/universal/.qoder/skills/citable/manifest.json +9 -6
- package/dist/universal/.qoder/skills/citable/schemas/cli-output-envelope.schema.json +30 -0
- package/dist/universal/.qoder/skills/citable/schemas/finding.schema.json +69 -0
- package/dist/universal/.qoder/skills/citable/schemas/remediation-verification.schema.json +98 -0
- package/dist/universal/.qoder/skills/citable/schemas/sow.schema.json +92 -0
- package/dist/universal/.rovodev/skills/citable/SKILL.md +66 -1
- package/dist/universal/.rovodev/skills/citable/VERSION +1 -1
- package/dist/universal/.rovodev/skills/citable/manifest.json +9 -6
- package/dist/universal/.rovodev/skills/citable/schemas/cli-output-envelope.schema.json +30 -0
- package/dist/universal/.rovodev/skills/citable/schemas/finding.schema.json +69 -0
- package/dist/universal/.rovodev/skills/citable/schemas/remediation-verification.schema.json +98 -0
- package/dist/universal/.rovodev/skills/citable/schemas/sow.schema.json +92 -0
- package/dist/universal/.trae/skills/citable/SKILL.md +66 -1
- package/dist/universal/.trae/skills/citable/VERSION +1 -1
- package/dist/universal/.trae/skills/citable/manifest.json +9 -6
- package/dist/universal/.trae/skills/citable/schemas/cli-output-envelope.schema.json +30 -0
- package/dist/universal/.trae/skills/citable/schemas/finding.schema.json +69 -0
- package/dist/universal/.trae/skills/citable/schemas/remediation-verification.schema.json +98 -0
- package/dist/universal/.trae/skills/citable/schemas/sow.schema.json +92 -0
- package/dist/universal/.trae-cn/skills/citable/SKILL.md +66 -1
- package/dist/universal/.trae-cn/skills/citable/VERSION +1 -1
- package/dist/universal/.trae-cn/skills/citable/manifest.json +9 -6
- package/dist/universal/.trae-cn/skills/citable/schemas/cli-output-envelope.schema.json +30 -0
- package/dist/universal/.trae-cn/skills/citable/schemas/finding.schema.json +69 -0
- package/dist/universal/.trae-cn/skills/citable/schemas/remediation-verification.schema.json +98 -0
- package/dist/universal/.trae-cn/skills/citable/schemas/sow.schema.json +92 -0
- package/dist/universal/manifest.json +112 -76
- package/package.json +1 -1
- package/schemas/cli-output-envelope.schema.json +30 -0
- package/schemas/finding.schema.json +69 -0
- package/schemas/remediation-verification.schema.json +98 -0
- package/schemas/sow.schema.json +92 -0
- package/skill/SKILL.md +66 -1
- package/src/analysis/behavioral.js +126 -0
- package/src/analysis/croAudit.js +302 -0
- package/src/analysis/croRoadmap.js +121 -0
- package/src/analysis/eeat.js +300 -0
- package/src/analysis/funnelAnalysis.js +171 -0
- package/src/analysis/iceMatrix.js +205 -0
- package/src/analysis/offpage.js +228 -0
- package/src/analysis/readiness.js +253 -0
- package/src/analysis/saliency.js +119 -0
- package/src/analysis/schemaRecommendations.js +173 -0
- package/src/analysis/strategicRoadmap.js +148 -0
- package/src/cli/index.js +418 -9
- package/src/commands/answerEngineReadiness.js +48 -0
- package/src/commands/audit.js +2 -2
- package/src/commands/auditBacklinks.js +79 -0
- package/src/commands/ciWorkflow.js +63 -0
- package/src/commands/compatibility.js +205 -0
- package/src/commands/context.js +2 -1
- package/src/commands/croBacklog.js +182 -0
- package/src/commands/croSuite.js +156 -0
- package/src/commands/edgeRules.js +80 -4
- package/src/commands/edgeSecurity.js +192 -0
- package/src/commands/executive.js +6 -0
- package/src/commands/executiveCro.js +30 -0
- package/src/commands/executiveSearch.js +28 -0
- package/src/commands/experimentGuardrails.js +213 -0
- package/src/commands/goldenCorpus.js +155 -0
- package/src/commands/implementationKit.js +202 -0
- package/src/commands/inspectCro.js +67 -0
- package/src/commands/inspectEeat.js +53 -0
- package/src/commands/planExperiment.js +52 -0
- package/src/commands/previewCro.js +241 -0
- package/src/commands/prioritize.js +42 -6
- package/src/commands/remediate.js +461 -0
- package/src/commands/roadmapCmd.js +69 -0
- package/src/commands/schemaCmd.js +10 -2
- package/src/commands/sowCmd.js +67 -0
- package/src/commands/sweep.js +357 -0
- package/src/commands/verifyRemediation.js +204 -0
- package/src/commands/visualRegression.js +165 -0
- package/src/components/index.js +556 -0
- package/src/detectors/cro.js +48 -0
- package/src/detectors/framework.js +31 -1
- package/src/reporting/executiveCroReport.js +731 -0
- package/src/reporting/executiveExport.js +175 -1
- package/src/reporting/executiveSearchReport.js +704 -0
- package/src/sow/admissibilityGate.js +194 -0
- package/src/sow/generateSow.js +762 -0
package/src/cli/index.js
CHANGED
|
@@ -1,8 +1,20 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import { auditCroSuite, formatCroSuiteOutput } from "../commands/croSuite.js";
|
|
3
|
+
import { formatBacklogMarkdown } from "../commands/croBacklog.js";
|
|
4
|
+
import { formatCroRoadmapMarkdown } from "../analysis/croRoadmap.js";
|
|
5
|
+
import { sweepTechnical, formatSweepOutput } from "../commands/sweep.js";
|
|
6
|
+
import { inspectEeat, formatEeatOutput } from "../commands/inspectEeat.js";
|
|
7
|
+
import { inspectReadiness, formatReadinessOutput } from "../commands/answerEngineReadiness.js";
|
|
8
|
+
import { auditBacklinks, formatBacklinksOutput } from "../commands/auditBacklinks.js";
|
|
9
|
+
import { roadmapCommand } from "../commands/roadmapCmd.js";
|
|
10
|
+
import { sowCommand } from "../commands/sowCmd.js";
|
|
11
|
+
import { buildIceMatrix, formatIceMatrixOutput } from "../analysis/iceMatrix.js";
|
|
12
|
+
import fs from 'node:fs';
|
|
2
13
|
import path from 'node:path';
|
|
3
14
|
import { pathToFileURL } from 'node:url';
|
|
4
15
|
import { init } from '../commands/init.js';
|
|
5
16
|
import { audit } from '../commands/audit.js';
|
|
17
|
+
import { auditEdgeCode } from '../commands/edgeSecurity.js';
|
|
6
18
|
import { planAudit } from '../commands/planAudit.js';
|
|
7
19
|
import { demo } from '../commands/demo.js';
|
|
8
20
|
import { validate } from '../commands/validate.js';
|
|
@@ -21,6 +33,14 @@ import { probeEngine } from '../commands/probe.js';
|
|
|
21
33
|
import { lintComponents } from '../commands/lintComponents.js';
|
|
22
34
|
import { exportExecutiveReport } from '../reporting/executiveExport.js';
|
|
23
35
|
import { attributeImpact } from '../commands/attributeImpact.js';
|
|
36
|
+
import { remediateCommand } from '../commands/remediate.js';
|
|
37
|
+
import { verifyRemediation } from '../commands/verifyRemediation.js';
|
|
38
|
+
import { exportImplementationKit } from '../commands/implementationKit.js';
|
|
39
|
+
import { compatibilityCommand, verifyPage } from '../commands/compatibility.js';
|
|
40
|
+
import { runVisualRegression } from '../commands/visualRegression.js';
|
|
41
|
+
import { checkExperiment } from '../commands/experimentGuardrails.js';
|
|
42
|
+
import { runGoldenBenchmark } from '../commands/goldenCorpus.js';
|
|
43
|
+
import { previewCroCommand } from '../commands/previewCro.js';
|
|
24
44
|
import { schemaCommand } from '../commands/schemaCmd.js';
|
|
25
45
|
import { compareSnapshots } from '../commands/compareSnapshots.js';
|
|
26
46
|
import { isInstallerCommand, runInstallerCommand } from '../installer/index.js';
|
|
@@ -76,12 +96,26 @@ Commands
|
|
|
76
96
|
inspect <page> Profile one page (URL path or source file)
|
|
77
97
|
inspect serp <page> Simulate Google SERP title/snippet truncation and rich results
|
|
78
98
|
inspect cro <page> Evaluate page conversion readiness, CTA visibility, and friction
|
|
99
|
+
inspect saliency <page> Evaluate privacy-first algorithmic visual attention and CTA conspicuity
|
|
79
100
|
inspect aeo <page> Evaluate answer extractability, question density, and structure
|
|
80
101
|
inspect geo <page> Evaluate RAG chunkability, section density, and retrieval posture
|
|
102
|
+
preview cro <page> Interactive split-screen visual preview of original vs remediated page
|
|
103
|
+
remediate [finding] Synthesize accessible Nebula Components; safe patches with diff, validation, confidence, rollback (--write gated, fail closed)
|
|
81
104
|
test funnel [id] Test multi-step conversion funnel continuity and parameter persistence
|
|
105
|
+
test visual Deterministic layout-contract checks + viewport/variant screenshot matrix (screenshots need playwright)
|
|
82
106
|
plan experiment Calculate statistical sample size and duration for A/B testing
|
|
107
|
+
check experiment Experiment guardrails: SRM, stopping, power, contamination, lifecycle status
|
|
83
108
|
map-claims Extract material claim candidates from pages (--write to save)
|
|
84
109
|
substantiate Assess claim/evidence status (--write to apply downgrades)
|
|
110
|
+
cro [audit] Full CRO intelligence suite: funnel, ATF clarity, trust, cognitive load, ICE matrix, roadmap
|
|
111
|
+
cro backlog Generate A/B experimentation backlog with falsifiable hypotheses & guardrails
|
|
112
|
+
cro roadmap 30/90/180-day CRO roadmap tied to measurable conversion outcomes
|
|
113
|
+
sweep [technical] Run technical SEO sweep with Core Web Vitals metrics
|
|
114
|
+
inspect eeat <page> Evaluate on-page content against Google E-E-A-T rubric (0-5 scale)
|
|
115
|
+
inspect readiness <page> Evaluate answer-engine readiness across Perplexity, Bing Copilot, and ChatGPT
|
|
116
|
+
audit backlinks Audit off-page authority and identify toxic domains (--input <file>)
|
|
117
|
+
prioritize matrix Impact/Effort/Confidence (ICE) scoring matrix for roadmap & findings
|
|
118
|
+
roadmap [generate|show] 30/90/180-day strategic roadmap with milestone horizons
|
|
85
119
|
schema Validate deployed JSON-LD and propose registry-derived schema
|
|
86
120
|
validate [mode] registries (default) | claims | evidence | schema | links
|
|
87
121
|
compare-snapshots [a b] Regression diff between two audit runs
|
|
@@ -94,6 +128,8 @@ Commands
|
|
|
94
128
|
report dashboard [--last N] [--since <run-id>] Render a cross-run evidence trend as Markdown + HTML
|
|
95
129
|
report share-of-voice [--last N] Compute first-party and competitor citation share
|
|
96
130
|
report consensus [--last N] [--since <run-id>] Render Canonical Discovery Consensus Matrix (Markdown + HTML)
|
|
131
|
+
report search [--target <dir|url>] [--run <id>] Enterprise Search Intelligence & AEO/GEO Executive Briefing (19 pillars)
|
|
132
|
+
report cro [--target <dir|url>] [--input <file>] Enterprise CRO & Customer Journey Executive Briefing (25 pillars)
|
|
97
133
|
metrics import Import declared metric observations from CSV/JSON
|
|
98
134
|
connect status List optional connectors and configured connections
|
|
99
135
|
connect configure Configure non-secret connection state (--write to save)
|
|
@@ -123,9 +159,14 @@ Commands
|
|
|
123
159
|
corpus evaluate Evaluate a disclosed real-property acceptance corpus
|
|
124
160
|
corpus publish Validate and project an owner-authorized public corpus
|
|
125
161
|
corpus receipt Create a reproducibility receipt for a sealed run
|
|
162
|
+
corpus benchmark Run the labeled golden fixture corpus and report per-detector precision/recall
|
|
126
163
|
corpus compare-receipts Compare two acceptance-run receipt envelopes
|
|
127
164
|
artifacts export Export one sealed run as a portable verified directory
|
|
128
165
|
artifacts verify Verify an exported artifact interchange directory
|
|
166
|
+
verify remediation Closed loop: re-run the detector after a patch and emit a before/after evidence bundle
|
|
167
|
+
verify page <page> Run all detectors scoped to one page and report posture (pass/attention/blocked)
|
|
168
|
+
compatibility Pre-flight: Node engine, optional adapters, browser, framework, registries, edge limits
|
|
169
|
+
kit export Customer-ready implementation kit: finding, diff, evidence, acceptance tests, deployment
|
|
129
170
|
artifacts import Import a verified run without changing its canonical bytes
|
|
130
171
|
self-upgrade Check for a newer version and upgrade the npx cache
|
|
131
172
|
kpi [list|show|validate] KPI architecture — govern metric definitions, sources, targets
|
|
@@ -203,6 +244,7 @@ function parseArgs(argv) {
|
|
|
203
244
|
else if (a === '--target') args.target = argv[++i];
|
|
204
245
|
else if (a === '--base-url') args.baseUrl = argv[++i];
|
|
205
246
|
else if (a === '--ref-date') args.refDate = argv[++i];
|
|
247
|
+
else if (a === '--viewport') args.viewport = argv[++i];
|
|
206
248
|
else if (a === '--input') args.input = argv[++i];
|
|
207
249
|
else if (a === '--output') args.output = argv[++i];
|
|
208
250
|
else if (a === '--run') args.runId = argv[++i];
|
|
@@ -254,14 +296,59 @@ function parseArgs(argv) {
|
|
|
254
296
|
else if (a === '--note') args.note = argv[++i];
|
|
255
297
|
else if (a === '--expired') args.expiredOnly = true;
|
|
256
298
|
else if (a === '--expiring-soon') args.expiringSoonDays = Number(argv[++i]);
|
|
299
|
+
else if (a === '--finding') args.finding = argv[++i];
|
|
300
|
+
else if (a === '--component') args.component = argv[++i];
|
|
301
|
+
else if (a === '--format') args.format = argv[++i];
|
|
302
|
+
else if (a === '--strict') args.strict = true;
|
|
303
|
+
else if (a === '--export') args.export = argv[++i];
|
|
304
|
+
else if (a === '--apply') args.apply = true;
|
|
305
|
+
else if (a === '--preview-file') args['preview-file'] = argv[++i];
|
|
306
|
+
else if (a === '--observed-control') args['observed-control'] = argv[++i];
|
|
307
|
+
else if (a === '--observed-variant') args['observed-variant'] = argv[++i];
|
|
308
|
+
else if (a === '--days-running') args['days-running'] = argv[++i];
|
|
309
|
+
else if (a === '--baseline-rate') args['baseline-rate'] = argv[++i];
|
|
310
|
+
else if (a === '--p-value') args['p-value'] = argv[++i];
|
|
311
|
+
else if (a === '--matrix') args.matrix = true;
|
|
312
|
+
else if (a === '--roadmap') args.roadmap = true;
|
|
313
|
+
else if (a === '--engines') args.engines = true;
|
|
314
|
+
else if (a === '--funnel') args.funnel = argv[++i];
|
|
315
|
+
else if (a === '--client') args.client = argv[++i];
|
|
316
|
+
else if (a === '--type') args.type = argv[++i];
|
|
317
|
+
else if (a === '--revenue-claimed') args['revenue-claimed'] = true;
|
|
257
318
|
else args._.push(a);
|
|
258
319
|
}
|
|
259
320
|
return args;
|
|
260
321
|
}
|
|
261
322
|
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
323
|
+
const TOOL_VERSION_CLI = JSON.parse(
|
|
324
|
+
fs.readFileSync(new URL('../../package.json', import.meta.url), 'utf8')
|
|
325
|
+
).version;
|
|
326
|
+
|
|
327
|
+
// Set by main(); read by out() for the JSON envelope. Kept off `args` because
|
|
328
|
+
// several commands spread parsed args directly into option objects.
|
|
329
|
+
let CURRENT_COMMAND = null;
|
|
330
|
+
|
|
331
|
+
/**
|
|
332
|
+
* Stable machine-readable output contract (citable_output_schema 1.0):
|
|
333
|
+
* every command's --json payload is wrapped in this envelope so CI systems
|
|
334
|
+
* and agencies can consume results without parsing terminal text. The
|
|
335
|
+
* envelope version changes only on breaking envelope changes; command
|
|
336
|
+
* payloads evolve backward-compatibly inside `result`.
|
|
337
|
+
*/
|
|
338
|
+
const OUTPUT_SCHEMA_VERSION = '1.0';
|
|
339
|
+
|
|
340
|
+
function out(args, human, data, command = null) {
|
|
341
|
+
if (args.json) {
|
|
342
|
+
console.log(JSON.stringify({
|
|
343
|
+
citable_output_schema: OUTPUT_SCHEMA_VERSION,
|
|
344
|
+
tool_version: TOOL_VERSION_CLI,
|
|
345
|
+
command: command ?? CURRENT_COMMAND,
|
|
346
|
+
generated_at: new Date().toISOString(),
|
|
347
|
+
result: data,
|
|
348
|
+
}, null, 2));
|
|
349
|
+
} else {
|
|
350
|
+
console.log(human);
|
|
351
|
+
}
|
|
265
352
|
}
|
|
266
353
|
|
|
267
354
|
export async function main(argv = process.argv.slice(2), options = {}) {
|
|
@@ -272,6 +359,7 @@ export async function main(argv = process.argv.slice(2), options = {}) {
|
|
|
272
359
|
}
|
|
273
360
|
if (isInstallerCommand(cmd)) return runInstallerCommand(cmd, argv.slice(1), options);
|
|
274
361
|
const args = parseArgs(argv.slice(1));
|
|
362
|
+
CURRENT_COMMAND = cmd;
|
|
275
363
|
const root = options.cwd ?? process.cwd();
|
|
276
364
|
|
|
277
365
|
try {
|
|
@@ -299,8 +387,30 @@ export async function main(argv = process.argv.slice(2), options = {}) {
|
|
|
299
387
|
break;
|
|
300
388
|
}
|
|
301
389
|
case 'audit': {
|
|
390
|
+
if (args._[0] === 'cro') {
|
|
391
|
+
const r = await auditCroSuite(root, { target: args.target, baseUrl: args.baseUrl, refDate: args.refDate, input: args.input, funnelId: args.funnel });
|
|
392
|
+
out(args, formatCroSuiteOutput(r), r);
|
|
393
|
+
break;
|
|
394
|
+
}
|
|
395
|
+
if (args._[0] === 'backlinks') {
|
|
396
|
+
const r = await auditBacklinks(root, { input: args.input, target: args.target, minSeverity: args.minSeverity });
|
|
397
|
+
out(args, formatBacklinksOutput(r), r);
|
|
398
|
+
break;
|
|
399
|
+
}
|
|
400
|
+
if (args._[0] === 'edge') {
|
|
401
|
+
const file = args._[1];
|
|
402
|
+
if (!file || !args.format) throw new Error('usage: citable audit edge <file> --format <cloudflare-worker|cloudflare-cro|vercel-middleware|shopify-snippet|html>');
|
|
403
|
+
const content = fs.readFileSync(path.resolve(root, file), 'utf8');
|
|
404
|
+
const r = auditEdgeCode(content, { format: args.format, name: file });
|
|
405
|
+
const txt = `audit edge ${file} (${r.format}, ${r.byte_size} bytes)
|
|
406
|
+
Findings: ${r.finding_count} (high:${r.high} medium:${r.medium} advisory:${r.advisory})
|
|
407
|
+
${r.findings.map((f) => ` [${f.rule_id}] (${f.severity}) ${f.summary}\n Fix: ${f.remediation}`).join('\n') || ' no risk patterns found'}
|
|
408
|
+
Methodology: ${r.methodology}`;
|
|
409
|
+
out(args, txt, r);
|
|
410
|
+
break;
|
|
411
|
+
}
|
|
302
412
|
const scope = args._[0];
|
|
303
|
-
const r = await audit(root, { target: args.target, scope, baseUrl: args.baseUrl, refDate: args.refDate });
|
|
413
|
+
const r = await audit(root, { target: args.target, scope, baseUrl: args.baseUrl, refDate: args.refDate, viewport: args.viewport });
|
|
304
414
|
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 });
|
|
305
415
|
break;
|
|
306
416
|
}
|
|
@@ -313,6 +423,18 @@ export async function main(argv = process.argv.slice(2), options = {}) {
|
|
|
313
423
|
}
|
|
314
424
|
case 'inspect': {
|
|
315
425
|
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>');
|
|
426
|
+
if (args._[0] === 'eeat' || args._[0] === 'content') {
|
|
427
|
+
if (!args._[1]) throw new Error('usage: citable inspect eeat <page> --target <dir|url>');
|
|
428
|
+
const r = await inspectEeat(root, args._[1], { target: args.target, baseUrl: args.baseUrl, refDate: args.refDate });
|
|
429
|
+
out(args, formatEeatOutput(r), r);
|
|
430
|
+
break;
|
|
431
|
+
}
|
|
432
|
+
if (args._[0] === 'readiness' || args._[0] === 'engines') {
|
|
433
|
+
if (!args._[1]) throw new Error('usage: citable inspect readiness <page> --target <dir|url>');
|
|
434
|
+
const r = await inspectReadiness(root, args._[1], { target: args.target, baseUrl: args.baseUrl, refDate: args.refDate });
|
|
435
|
+
out(args, formatReadinessOutput(r), r);
|
|
436
|
+
break;
|
|
437
|
+
}
|
|
316
438
|
if (args._[0] === 'serp') {
|
|
317
439
|
if (!args._[1]) throw new Error('usage: citable inspect serp <page> --target <dir|url>');
|
|
318
440
|
const r = await inspectSerp(root, args._[1], { target: args.target, baseUrl: args.baseUrl, refDate: args.refDate });
|
|
@@ -336,11 +458,18 @@ ${richList}
|
|
|
336
458
|
if (!args._[1]) throw new Error('usage: citable inspect cro <page> --target <dir|url>');
|
|
337
459
|
const r = await inspectCro(root, args._[1], { target: args.target, baseUrl: args.baseUrl, refDate: args.refDate });
|
|
338
460
|
const txt = `Inspect CRO ${r.url}
|
|
339
|
-
Status: ${r.conversion_status}
|
|
461
|
+
Status: ${r.conversion_status} (Conversion Readiness: ${r.conversion_readiness_score ?? 'N/A'}/100)
|
|
462
|
+
Above-the-Fold Clarity: ${r.atf_clarity?.score ?? 'N/A'}/100 (Scent: ${r.atf_clarity?.scent_match ? 'CONGRUENT' : 'GAP'})
|
|
463
|
+
Trust & Credibility: ${r.trust_and_credibility?.score ?? 'N/A'}/100
|
|
464
|
+
Friction Surface Area: ${r.friction_surface_area}
|
|
340
465
|
Declared Conversion Action: ${r.declared_conversion_action || 'none'}
|
|
341
466
|
Page Type: ${r.page_type || 'unregistered'}; Intent: ${r.primary_intent || 'unregistered'}
|
|
342
467
|
Analytics Instrumentation: ${r.analytics_installed ? `INSTALLED (${r.analytics_tags.length} tag${r.analytics_tags.length === 1 ? '' : 's'})` : 'NOT DETECTED'}
|
|
343
468
|
Hero CTAs: ${r.hero_cta_count}; Choice Overload: ${r.has_choice_overload ? 'YES (paralysis risk)' : 'no'}
|
|
469
|
+
Primary CTA Conspicuity: ${r.saliency?.primary_cta_conspicuity_index ?? 'N/A'} (${r.saliency?.pci_assessment ?? 'unassessed'}) — modeled heuristic index, not observed attention
|
|
470
|
+
Keystroke Effort: ${r.keystroke_effort?.autofill_coverage_pct}% autofill coverage (${r.keystroke_effort?.keystroke_reduction_pct}% modeled mobile friction reduction)
|
|
471
|
+
Payment Wallet Readiness: ${r.express_checkout_readiness?.payment_wallet_readiness?.supported ? 'SUPPORTED' : 'NOT DETECTED (CRO-021)'}
|
|
472
|
+
Authentication Readiness (passkeys/WebAuthn): ${r.express_checkout_readiness?.authentication_readiness?.supported ? 'DETECTED' : 'NOT DETECTED'} (informational index; authentication, not payment)
|
|
344
473
|
CTAs (${r.ctas.length}):
|
|
345
474
|
${r.ctas.map((c) => ` [${c.isPrimary ? 'PRIMARY' : 'SECONDARY'}${c.inHero ? ' - HERO' : ''}] "${c.text}" (${c.tag}) → ${c.target || 'no target'}`).join('\n') || ' none'}
|
|
346
475
|
Forms (${r.forms.length}):
|
|
@@ -350,6 +479,20 @@ ${r.forms.map((f, i) => ` Form ${i + 1}: ${f.fieldCount} fields (autocomplete
|
|
|
350
479
|
out(args, txt, r);
|
|
351
480
|
break;
|
|
352
481
|
}
|
|
482
|
+
if (args._[0] === 'saliency') {
|
|
483
|
+
if (!args._[1]) throw new Error('usage: citable inspect saliency <page> --target <dir|url>');
|
|
484
|
+
const r = await inspectCro(root, args._[1], { target: args.target, baseUrl: args.baseUrl, refDate: args.refDate });
|
|
485
|
+
const sal = r.saliency;
|
|
486
|
+
const gazeLines = (sal?.predicted_gaze_path || []).map((g) => ` ${g.order}. [${g.type.toUpperCase()}] "${g.label}" (salience: ${Math.round(g.salience_score * 100)}%)`).join('\n');
|
|
487
|
+
const txt = `Inspect Saliency ${r.url}
|
|
488
|
+
Primary CTA Conspicuity Index: ${sal?.primary_cta_conspicuity_index ?? 'N/A'} [${sal?.pci_assessment ?? 'unassessed'}]
|
|
489
|
+
Visual Clutter Risk: ${sal?.visual_clutter ?? 'clean'} (${sal?.competing_hero_elements_count ?? 0} high-salience hero elements)
|
|
490
|
+
Predicted Gaze Path (First 3 Fixations):
|
|
491
|
+
${gazeLines || ' none predicted'}
|
|
492
|
+
SVG Attention Heatmap: generated (${sal?.elements_evaluated ?? 0} elements mapped)`;
|
|
493
|
+
out(args, txt, { url: r.url, saliency: sal });
|
|
494
|
+
break;
|
|
495
|
+
}
|
|
353
496
|
if (args._[0] === 'aeo') {
|
|
354
497
|
if (!args._[1]) throw new Error('usage: citable inspect aeo <page> --target <dir|url>');
|
|
355
498
|
const r = await inspectAeo(root, args._[1], { target: args.target, baseUrl: args.baseUrl, refDate: args.refDate });
|
|
@@ -451,8 +594,84 @@ ${r.headings.questions.map((q) => ` [H${q.level}] "${q.text}"`).join('\n') ||
|
|
|
451
594
|
out(args, txt, r);
|
|
452
595
|
break;
|
|
453
596
|
}
|
|
597
|
+
case 'preview': {
|
|
598
|
+
const sub = args._[0];
|
|
599
|
+
if (sub !== 'cro') throw new Error('usage: citable preview cro <page> [--target <dir|url>] [--export <path>]');
|
|
600
|
+
const page = args._[1] || 'home';
|
|
601
|
+
const r = await previewCroCommand(root, page, { target: args.target, export: args.export });
|
|
602
|
+
const txt = `Preview CRO ${r.url}
|
|
603
|
+
Friction Surface Area (Before): ${r.fsa_before}
|
|
604
|
+
Friction Surface Area (Modeled Target): ${r.fsa_after} (modeled index, not a measured outcome)
|
|
605
|
+
Resolved Findings: ${r.findings_count}
|
|
606
|
+
HTML Preview: ${r.saved_file || 'rendered in memory (use --export <file.html> to save)'}`;
|
|
607
|
+
out(args, txt, r);
|
|
608
|
+
break;
|
|
609
|
+
}
|
|
610
|
+
case 'remediate': {
|
|
611
|
+
const r = await remediateCommand(root, {
|
|
612
|
+
finding: args.finding || args._[0],
|
|
613
|
+
component: args.component,
|
|
614
|
+
target: args.target,
|
|
615
|
+
format: args.format || 'react',
|
|
616
|
+
write: args.write,
|
|
617
|
+
});
|
|
618
|
+
if (r.target) {
|
|
619
|
+
const parts = [`Remediate ${r.finding_id} → ${r.recommended_component}`, ` Rationale: ${r.rationale}`, ` Scaffold Command: ${r.scaffold_command}`];
|
|
620
|
+
parts.push(` Target File: ${r.target}`);
|
|
621
|
+
parts.push(` Framework: ${r.framework?.framework || 'unknown'} (detection confidence ${r.framework?.confidence ?? 0})`);
|
|
622
|
+
parts.push(` Patch: ${r.changed ? `${r.match_count} match(es)` : 'no match (already remediated or not applicable)'}, idempotent: ${r.idempotent}, class: ${r.patcher_class}`);
|
|
623
|
+
if (r.validation) {
|
|
624
|
+
for (const c of r.validation.checks) parts.push(` Check ${c.check_id}: ${c.passed ? 'PASS' : 'FAIL'} — ${c.detail}`);
|
|
625
|
+
if (r.validation.methodology) parts.push(` Methodology: ${r.validation.methodology}`);
|
|
626
|
+
}
|
|
627
|
+
if (r.confidence) {
|
|
628
|
+
parts.push(` Confidence: ${r.confidence.score} (write threshold ${r.write_policy?.min_confidence ?? 0.7})`);
|
|
629
|
+
for (const f of r.confidence.factors) parts.push(` - ${f.factor}: ${f.weight} (${f.detail})`);
|
|
630
|
+
}
|
|
631
|
+
parts.push(r.written
|
|
632
|
+
? ` Written. Rollback snapshot: ${r.rollback?.snapshot_file}`
|
|
633
|
+
: r.write_refused
|
|
634
|
+
? ` Write REFUSED (fail closed): ${r.refusal_reason}`
|
|
635
|
+
: ` Dry run — unified diff below. Use --write to apply.`);
|
|
636
|
+
if (r.diff) parts.push('Unified Diff:', r.diff.trimEnd());
|
|
637
|
+
parts.push('', 'Code Snippet (recommended component):', '--------------------------------------------------', r.code, '--------------------------------------------------');
|
|
638
|
+
out(args, parts.join('\n'), r);
|
|
639
|
+
break;
|
|
640
|
+
}
|
|
641
|
+
const txt = r.recommended_component
|
|
642
|
+
? `Remediate ${r.finding_id} → ${r.recommended_component}
|
|
643
|
+
Rationale: ${r.rationale}
|
|
644
|
+
Scaffold Command: ${r.scaffold_command}
|
|
645
|
+
Code Snippet (${r.format}):
|
|
646
|
+
--------------------------------------------------
|
|
647
|
+
${r.code}
|
|
648
|
+
--------------------------------------------------`
|
|
649
|
+
: r.component
|
|
650
|
+
? `Remediate Component: ${r.component}
|
|
651
|
+
Scaffold Command: ${r.scaffold_command}
|
|
652
|
+
Code Snippet:
|
|
653
|
+
--------------------------------------------------
|
|
654
|
+
${r.code}
|
|
655
|
+
--------------------------------------------------`
|
|
656
|
+
: `Available Components:
|
|
657
|
+
${(r.available_components || []).map((c) => ` - ${c.name} (${c.id}): ${c.description}`).join('\n')}
|
|
658
|
+
Supported Findings: ${(r.supported_findings || []).join(', ')}`;
|
|
659
|
+
out(args, txt, r);
|
|
660
|
+
break;
|
|
661
|
+
}
|
|
454
662
|
case 'test-funnel':
|
|
455
663
|
case 'test': {
|
|
664
|
+
if (cmd === 'test' && args._[0] === 'visual') {
|
|
665
|
+
const preview = args['preview-file'] ? fs.readFileSync(path.resolve(root, args['preview-file']), 'utf8') : null;
|
|
666
|
+
const r = await runVisualRegression(root, { previewHtml: preview, output: args.output });
|
|
667
|
+
const txt = `test visual
|
|
668
|
+
Contract checks: ${r.contract.ok ? 'ALL PASS' : 'FAIL'} (${r.contract.checks.filter((c) => c.passed).length}/${r.contract.checks.length})
|
|
669
|
+
${r.contract.checks.map((c) => ` ${c.passed ? '✔' : '✖'} ${c.check_id}: ${c.detail}`).join('\n')}
|
|
670
|
+
Matrix cells: ${r.matrix_cells.length}
|
|
671
|
+
Screenshots: ${r.screenshots.status}${r.screenshots.required_input ? ` (required_input: ${r.screenshots.required_input})` : ` (${r.screenshots.captured.length} captured)`}`;
|
|
672
|
+
out(args, txt, r);
|
|
673
|
+
break;
|
|
674
|
+
}
|
|
456
675
|
if (cmd === 'test-funnel' || args._[0] === 'funnel') {
|
|
457
676
|
const funnelId = cmd === 'test-funnel' ? args._[0] : args._[1];
|
|
458
677
|
const r = await testFunnel(root, funnelId, { target: args.target, baseUrl: args.baseUrl, refDate: args.refDate });
|
|
@@ -510,7 +729,8 @@ ${r.issues.map((iss) => ` - ${iss}`).join('\n') || ' none'}
|
|
|
510
729
|
if (sub === 'edge') {
|
|
511
730
|
const format = args.format || 'cloudflare-redirects';
|
|
512
731
|
const r = await exportEdgeRules(root, { format, output: args.output });
|
|
513
|
-
|
|
732
|
+
const sec = r.security;
|
|
733
|
+
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);
|
|
514
734
|
break;
|
|
515
735
|
}
|
|
516
736
|
throw new Error('usage: citable export edge [--format <cloudflare-redirects|cloudflare-waf|cloudflare-worker>]');
|
|
@@ -577,6 +797,16 @@ ${r.recommendations.map((rec) => ` - ${rec}`).join('\n')}`;
|
|
|
577
797
|
break;
|
|
578
798
|
}
|
|
579
799
|
case 'action-plan': {
|
|
800
|
+
if (args.matrix) {
|
|
801
|
+
const r = await prioritizeCommand(['matrix', ...(args._[0] ? ['--run', args._[0]] : ['--findings'])], root);
|
|
802
|
+
out(args, r.formatted || JSON.stringify(r, null, 2), r);
|
|
803
|
+
break;
|
|
804
|
+
}
|
|
805
|
+
if (args.roadmap) {
|
|
806
|
+
const r = await roadmapCommand(root, { runId: args._[0], target: args.target, write: args.write !== false });
|
|
807
|
+
out(args, r.markdown, r);
|
|
808
|
+
break;
|
|
809
|
+
}
|
|
580
810
|
const r = actionPlan(root, { runId: args._[0] });
|
|
581
811
|
out(args, `action-plan: ${r.summary.total_actions} action(s) [ready:${r.summary.ready} blocked:${r.summary.blocked}]\nPlan: ${path.join(r.dir, 'action-plan.md')}\nSource audit: ${r.source_run_id}`, r);
|
|
582
812
|
break;
|
|
@@ -629,11 +859,46 @@ ${r.recommendations.map((rec) => ` - ${rec}`).join('\n')}`;
|
|
|
629
859
|
} else if (sub === 'consensus') {
|
|
630
860
|
const r = reportConsensus(root, { runId: args.runId, since: args.since, last: args.last ? Number(args.last) : undefined });
|
|
631
861
|
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);
|
|
862
|
+
} else if (sub === 'search' || sub === 'seo') {
|
|
863
|
+
const r = await exportExecutiveReport(root, args.runId, {
|
|
864
|
+
type: 'search',
|
|
865
|
+
format: args.format || 'markdown',
|
|
866
|
+
clientName: args.client,
|
|
867
|
+
output: args.output,
|
|
868
|
+
target: args.target,
|
|
869
|
+
baseUrl: args.baseUrl,
|
|
870
|
+
refDate: args.refDate,
|
|
871
|
+
input: args.input,
|
|
872
|
+
});
|
|
873
|
+
out(args, r.content, r.data);
|
|
874
|
+
} else if (sub === 'cro') {
|
|
875
|
+
const r = await exportExecutiveReport(root, args.runId, {
|
|
876
|
+
type: 'cro',
|
|
877
|
+
format: args.format || 'markdown',
|
|
878
|
+
clientName: args.client,
|
|
879
|
+
output: args.output,
|
|
880
|
+
target: args.target,
|
|
881
|
+
baseUrl: args.baseUrl,
|
|
882
|
+
refDate: args.refDate,
|
|
883
|
+
input: args.input,
|
|
884
|
+
funnelId: args.funnel,
|
|
885
|
+
});
|
|
886
|
+
out(args, r.content, r.data);
|
|
632
887
|
} else if (sub === 'export') {
|
|
633
|
-
const r = await exportExecutiveReport(root, args.runId, {
|
|
888
|
+
const r = await exportExecutiveReport(root, args.runId, {
|
|
889
|
+
format: args.format,
|
|
890
|
+
clientName: args.client,
|
|
891
|
+
output: args.output,
|
|
892
|
+
type: args.type,
|
|
893
|
+
target: args.target,
|
|
894
|
+
baseUrl: args.baseUrl,
|
|
895
|
+
refDate: args.refDate,
|
|
896
|
+
input: args.input,
|
|
897
|
+
funnelId: args.funnel,
|
|
898
|
+
});
|
|
634
899
|
out(args, `Executive Report Exported (${r.format}) for "${r.client_name}"${r.output_path ? ` to ${r.output_path}` : ''}\n\n${r.content}`, r);
|
|
635
900
|
} else {
|
|
636
|
-
throw new Error('usage: citable report <dashboard|share-of-voice|consensus|export> [--last <n>] [--since <run-id>] [--run <run-id>] [--format <html
|
|
901
|
+
throw new Error('usage: citable report <dashboard|share-of-voice|consensus|search|cro|export> [--last <n>] [--since <run-id>] [--run <run-id>] [--format <md|html|json>]');
|
|
637
902
|
}
|
|
638
903
|
break;
|
|
639
904
|
}
|
|
@@ -831,7 +1096,14 @@ ${r.recommendations.map((rec) => ` - ${rec}`).join('\n')}`;
|
|
|
831
1096
|
} else if (mode === 'compare-receipts') {
|
|
832
1097
|
const r = readAndCompareAcceptanceReceipts(args._[1], args._[2]);
|
|
833
1098
|
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);
|
|
834
|
-
} else
|
|
1099
|
+
} else if (mode === 'benchmark') {
|
|
1100
|
+
const r = await runGoldenBenchmark(root, { corpusDir: args.input, writeReport: args.output });
|
|
1101
|
+
const txt = `corpus benchmark v${r.corpus_version}: ${r.pages_evaluated} page(s) across ${r.sites_evaluated} site(s)
|
|
1102
|
+
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'})
|
|
1103
|
+
${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')}
|
|
1104
|
+
Limitations: ${r.limitations[0]}`;
|
|
1105
|
+
out(args, txt, r);
|
|
1106
|
+
} else throw new Error('usage: citable corpus <evaluate|publish|receipt|compare-receipts|benchmark> [options]');
|
|
835
1107
|
break;
|
|
836
1108
|
}
|
|
837
1109
|
case 'artifacts': {
|
|
@@ -848,6 +1120,93 @@ ${r.recommendations.map((rec) => ` - ${rec}`).join('\n')}`;
|
|
|
848
1120
|
} else throw new Error('usage: citable artifacts <export <run-id> --output <directory>|verify --input <directory>|import --input <directory>>');
|
|
849
1121
|
break;
|
|
850
1122
|
}
|
|
1123
|
+
case 'verify': {
|
|
1124
|
+
const mode = args._[0];
|
|
1125
|
+
if (mode === 'page') {
|
|
1126
|
+
const r = await verifyPage(root, args._[1], { target: args.target, baseUrl: args.baseUrl, refDate: args.refDate, viewport: args.viewport });
|
|
1127
|
+
const sev = Object.entries(r.by_severity).filter(([, n]) => n > 0).map(([k, n]) => `${k}:${n}`).join(' ') || 'none';
|
|
1128
|
+
const txt = `verify page ${r.page}
|
|
1129
|
+
Status: ${r.status}
|
|
1130
|
+
Findings: ${r.finding_count} (${sev})
|
|
1131
|
+
Detectors run: ${r.provenance.detectors_run.length}
|
|
1132
|
+
Definition: ${r.provenance.definition}`;
|
|
1133
|
+
out(args, txt, r);
|
|
1134
|
+
break;
|
|
1135
|
+
}
|
|
1136
|
+
if (mode !== 'remediation') throw new Error('usage: citable verify <remediation --run <run-id> --finding <detector-id> [|page <page>] [--target <dir|file>] [--apply]');
|
|
1137
|
+
const r = await verifyRemediation(root, {
|
|
1138
|
+
run: args.runId || args._[1],
|
|
1139
|
+
finding: args.finding,
|
|
1140
|
+
target: args.target,
|
|
1141
|
+
apply: args.apply,
|
|
1142
|
+
subject: args.subject,
|
|
1143
|
+
baseUrl: args['base-url'],
|
|
1144
|
+
refDate: args['ref-date'],
|
|
1145
|
+
});
|
|
1146
|
+
const subject = r.subject?.identifier || 'unknown subject';
|
|
1147
|
+
const patchLine = r.patch
|
|
1148
|
+
? `\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}` : ''}`
|
|
1149
|
+
: '';
|
|
1150
|
+
const txt = `verify remediation ${r.detector_id} on ${subject}
|
|
1151
|
+
Verdict: ${r.status}${r.verdict.resolved ? ' (finding no longer reported)' : ''}
|
|
1152
|
+
Before finding(s): ${r.verdict.before_finding_ids.join(', ') || 'none'}
|
|
1153
|
+
After finding(s): ${r.verdict.after_finding_ids.join(', ') || 'none'}
|
|
1154
|
+
New critical/high regressions: ${r.verdict.new_regressions.length}${patchLine}
|
|
1155
|
+
Re-check target: ${r.provenance.recheck_target || 'n/a'}
|
|
1156
|
+
Resolution criterion: detector absence for the same subject; not an outcome guarantee
|
|
1157
|
+
Bundle: ${r.bundle_dir || 'not written'}`;
|
|
1158
|
+
out(args, txt, r);
|
|
1159
|
+
break;
|
|
1160
|
+
}
|
|
1161
|
+
case 'kit': {
|
|
1162
|
+
if (args._[0] !== 'export') throw new Error('usage: citable kit export --run <run-id> --finding <detector-id> [--target <file>] [--subject <id|url>] [--output <dir>]');
|
|
1163
|
+
const r = await exportImplementationKit(root, {
|
|
1164
|
+
run: args.runId,
|
|
1165
|
+
finding: args.finding,
|
|
1166
|
+
target: args.target,
|
|
1167
|
+
subject: args.subject,
|
|
1168
|
+
output: args.output,
|
|
1169
|
+
format: args.format,
|
|
1170
|
+
});
|
|
1171
|
+
const txt = `kit export ${r.detector_id}
|
|
1172
|
+
Kit directory: ${r.kit_dir}
|
|
1173
|
+
Files: ${r.files.length}
|
|
1174
|
+
Rendering evidence files: ${r.rendering_evidence_count}
|
|
1175
|
+
Patch diff included: ${r.has_patch_diff}`;
|
|
1176
|
+
out(args, txt, r);
|
|
1177
|
+
break;
|
|
1178
|
+
}
|
|
1179
|
+
case 'compatibility': {
|
|
1180
|
+
const r = await compatibilityCommand(root, args);
|
|
1181
|
+
const txt = `compatibility (citable ${r.tool_version}, Node ${r.node})
|
|
1182
|
+
${r.checks.map((c) => ` [${c.severity.toUpperCase()}] ${c.check_id}: ${c.detail}`).join('\n')}
|
|
1183
|
+
Blockers: ${r.blocker_count}
|
|
1184
|
+
Note: ${r.note}`;
|
|
1185
|
+
out(args, txt, r);
|
|
1186
|
+
break;
|
|
1187
|
+
}
|
|
1188
|
+
case 'check': {
|
|
1189
|
+
if (args._[0] === 'experiment') {
|
|
1190
|
+
const r = await checkExperiment(root, {
|
|
1191
|
+
experimentId: args._[1],
|
|
1192
|
+
observedControl: args['observed-control'] !== undefined ? Number(args['observed-control']) : undefined,
|
|
1193
|
+
observedVariant: args['observed-variant'] !== undefined ? Number(args['observed-variant']) : undefined,
|
|
1194
|
+
daysRunning: args['days-running'] !== undefined ? Number(args['days-running']) : undefined,
|
|
1195
|
+
baselineRate: args['baseline-rate'],
|
|
1196
|
+
mde: args.mde,
|
|
1197
|
+
pValue: args['p-value'],
|
|
1198
|
+
revenueClaimed: args['revenue-claimed'],
|
|
1199
|
+
});
|
|
1200
|
+
const txt = `check experiment ${r.experiment_id}
|
|
1201
|
+
Lifecycle: ${r.lifecycle} — ${r.lifecycle_definition}
|
|
1202
|
+
Guardrails: ${r.guardrail_count} finding(s)
|
|
1203
|
+
${r.findings.map((f) => ` [${f.guardrail_id}] (${f.severity}) ${f.summary}`).join('\n') || ' no guardrail findings'}
|
|
1204
|
+
Note: ${r.note}`;
|
|
1205
|
+
out(args, txt, r);
|
|
1206
|
+
break;
|
|
1207
|
+
}
|
|
1208
|
+
throw new Error('usage: citable check experiment <id> [--observed-control N --observed-variant N --days-running N --p-value X --revenue-claimed]');
|
|
1209
|
+
}
|
|
851
1210
|
case 'self-upgrade': {
|
|
852
1211
|
const output = await selfUpgradeCommand(argv.slice(1));
|
|
853
1212
|
console.log(output);
|
|
@@ -915,6 +1274,56 @@ ${r.recommendations.map((rec) => ` - ${rec}`).join('\n')}`;
|
|
|
915
1274
|
out(args, JSON.stringify(r, null, 2), r);
|
|
916
1275
|
break;
|
|
917
1276
|
}
|
|
1277
|
+
case 'cro': {
|
|
1278
|
+
const sub = args._[0];
|
|
1279
|
+
if (sub === 'backlog') {
|
|
1280
|
+
const r = await auditCroSuite(root, { target: args.target, baseUrl: args.baseUrl, refDate: args.refDate, input: args.input });
|
|
1281
|
+
out(args, formatBacklogMarkdown(r.experiment_backlog), r.experiment_backlog);
|
|
1282
|
+
break;
|
|
1283
|
+
}
|
|
1284
|
+
if (sub === 'roadmap') {
|
|
1285
|
+
const r = await auditCroSuite(root, { target: args.target, baseUrl: args.baseUrl, refDate: args.refDate, input: args.input });
|
|
1286
|
+
out(args, formatCroRoadmapMarkdown(r.strategic_roadmap), r.strategic_roadmap);
|
|
1287
|
+
break;
|
|
1288
|
+
}
|
|
1289
|
+
const r = await auditCroSuite(root, { target: args.target, baseUrl: args.baseUrl, refDate: args.refDate, input: args.input, funnelId: args.funnel });
|
|
1290
|
+
out(args, formatCroSuiteOutput(r), r);
|
|
1291
|
+
break;
|
|
1292
|
+
}
|
|
1293
|
+
case 'sweep': {
|
|
1294
|
+
const sub = args._[0];
|
|
1295
|
+
if (sub && sub !== 'technical') throw new Error('usage: citable sweep technical [--target <dir|url>]');
|
|
1296
|
+
const r = await sweepTechnical(root, { target: args.target, baseUrl: args.baseUrl, refDate: args.refDate, page: args._[1] });
|
|
1297
|
+
out(args, formatSweepOutput(r), r);
|
|
1298
|
+
break;
|
|
1299
|
+
}
|
|
1300
|
+
case 'matrix': {
|
|
1301
|
+
const r = await prioritizeCommand(['matrix', ...argv.slice(1)], root);
|
|
1302
|
+
out(args, r.formatted || JSON.stringify(r, null, 2), r);
|
|
1303
|
+
break;
|
|
1304
|
+
}
|
|
1305
|
+
case 'roadmap': {
|
|
1306
|
+
const r = await roadmapCommand(root, { runId: args.runId, target: args.target, write: args.write !== false });
|
|
1307
|
+
out(args, r.markdown, r);
|
|
1308
|
+
break;
|
|
1309
|
+
}
|
|
1310
|
+
case 'sow': {
|
|
1311
|
+
const r = await sowCommand(argv.slice(1), root);
|
|
1312
|
+
out(args, r.content || r.message || JSON.stringify(r, null, 2), r.data || r);
|
|
1313
|
+
break;
|
|
1314
|
+
}
|
|
1315
|
+
case 'inspect-eeat': {
|
|
1316
|
+
if (!args._[0]) throw new Error('usage: citable inspect-eeat <page> --target <dir|url>');
|
|
1317
|
+
const r = await inspectEeat(root, args._[0], { target: args.target, baseUrl: args.baseUrl, refDate: args.refDate });
|
|
1318
|
+
out(args, formatEeatOutput(r), r);
|
|
1319
|
+
break;
|
|
1320
|
+
}
|
|
1321
|
+
case 'inspect-readiness': {
|
|
1322
|
+
if (!args._[0]) throw new Error('usage: citable inspect-readiness <page> --target <dir|url>');
|
|
1323
|
+
const r = await inspectReadiness(root, args._[0], { target: args.target, baseUrl: args.baseUrl, refDate: args.refDate });
|
|
1324
|
+
out(args, formatReadinessOutput(r), r);
|
|
1325
|
+
break;
|
|
1326
|
+
}
|
|
918
1327
|
case undefined:
|
|
919
1328
|
console.log(HELP);
|
|
920
1329
|
break;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { buildContext } from './context.js';
|
|
2
|
+
import { safePath } from '../detectors/framework.js';
|
|
3
|
+
import { evaluateAnswerEngineReadiness } from '../analysis/readiness.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* `citable inspect readiness <page>` — evaluate page extraction and readiness across
|
|
7
|
+
* Perplexity, Bing Copilot, and ChatGPT / SearchGPT.
|
|
8
|
+
*/
|
|
9
|
+
export async function inspectReadiness(root, pageRef, { target, baseUrl, refDate } = {}) {
|
|
10
|
+
const ctx = await buildContext(root, { target, baseUrl, refDate });
|
|
11
|
+
if (!ctx.site) throw new Error('inspect readiness requires a site target (built output directory or URL)');
|
|
12
|
+
const wanted = safePath(pageRef, ctx.site.baseUrl);
|
|
13
|
+
const page = ctx.site.pages.find((p) => safePath(p.url) === wanted || p.sourceFile === pageRef);
|
|
14
|
+
if (!page) throw new Error(`page not found in audited output: ${pageRef}`);
|
|
15
|
+
|
|
16
|
+
return evaluateAnswerEngineReadiness(page, ctx);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Format terminal output for `citable inspect readiness <page>`
|
|
21
|
+
*/
|
|
22
|
+
export function formatReadinessOutput(r) {
|
|
23
|
+
const lines = [
|
|
24
|
+
`Answer-Engine Readiness: ${r.url}`,
|
|
25
|
+
`==============================================`,
|
|
26
|
+
`Composite Readiness Score: ${r.composite_readiness_score} / 100`,
|
|
27
|
+
``,
|
|
28
|
+
`ENGINE EVALUATIONS:`,
|
|
29
|
+
` Perplexity: ${r.engines.perplexity.score}/100 [${r.engines.perplexity.status.toUpperCase()}] (Bot: ${r.engines.perplexity.primary_bot})`,
|
|
30
|
+
` Bing Copilot: ${r.engines.bing_copilot.score}/100 [${r.engines.bing_copilot.status.toUpperCase()}] (Bot: ${r.engines.bing_copilot.primary_bot})`,
|
|
31
|
+
` ChatGPT: ${r.engines.chatgpt.score}/100 [${r.engines.chatgpt.status.toUpperCase()}] (Bot: ${r.engines.chatgpt.primary_bot})`,
|
|
32
|
+
``,
|
|
33
|
+
`CROSS-ENGINE CAPABILITY MATRIX:`,
|
|
34
|
+
];
|
|
35
|
+
|
|
36
|
+
for (const row of r.comparison_matrix) {
|
|
37
|
+
lines.push(` - ${row.capability.padEnd(28)} | Perplexity: ${row.perplexity.padEnd(10)} | Copilot: ${row.bing_copilot.padEnd(14)} | ChatGPT: ${row.chatgpt}`);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (r.recommendations.length) {
|
|
41
|
+
lines.push(``, `PRIORITIZED ACTIONS:`);
|
|
42
|
+
for (const rec of r.recommendations) {
|
|
43
|
+
lines.push(` [${rec.priority.toUpperCase()}] (${rec.engine.toUpperCase()}) ${rec.action}`);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return lines.join('\n');
|
|
48
|
+
}
|
package/src/commands/audit.js
CHANGED
|
@@ -12,8 +12,8 @@ import { buildEntityGraph } from '../observations/entityGraph.js';
|
|
|
12
12
|
import { buildSourceIdentityChain } from '../observations/sourceIdentity.js';
|
|
13
13
|
|
|
14
14
|
/** `citable audit [scope]` — run detectors and produce an evidence package. */
|
|
15
|
-
export async function audit(root, { target, scope, baseUrl, refDate } = {}) {
|
|
16
|
-
const ctx = await buildContext(root, { target, baseUrl, refDate });
|
|
15
|
+
export async function audit(root, { target, scope, baseUrl, refDate, viewport = null } = {}) {
|
|
16
|
+
const ctx = await buildContext(root, { target, baseUrl, refDate, viewport });
|
|
17
17
|
const detectors = selectDetectors({ scope });
|
|
18
18
|
|
|
19
19
|
const run = createRun(root, {
|