@nebulacomponents/citable 1.13.1 → 1.15.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 +312 -0
- package/README.md +44 -6
- package/dist/universal/.agents/skills/citable/SKILL.md +22 -8
- package/dist/universal/.agents/skills/citable/VERSION +1 -1
- package/dist/universal/.agents/skills/citable/agents/citable-auditor.md +64 -0
- package/dist/universal/.agents/skills/citable/agents/citable-semantic-reviewer.md +66 -0
- package/dist/universal/.agents/skills/citable/commands/README.md +4 -1
- package/dist/universal/.agents/skills/citable/commands/action-findings.md +9 -1
- package/dist/universal/.agents/skills/citable/commands/observe-and-act.md +62 -8
- package/dist/universal/.agents/skills/citable/commands/plan-audit.md +35 -0
- package/dist/universal/.agents/skills/citable/commands/reporting.md +57 -0
- package/dist/universal/.agents/skills/citable/manifest.json +29 -16
- package/dist/universal/.agents/skills/citable/schemas/action-plan.schema.json +13 -0
- package/dist/universal/.agents/skills/citable/schemas/browser-evidence-plan.schema.json +14 -0
- package/dist/universal/.agents/skills/citable/schemas/canonical-index-import.schema.json +30 -0
- package/dist/universal/.agents/skills/citable/schemas/common.defs.json +12 -1
- package/dist/universal/.agents/skills/citable/schemas/corroboration-import.schema.json +48 -0
- package/dist/universal/.agents/skills/citable/schemas/entity-graph.schema.json +75 -0
- package/dist/universal/.agents/skills/citable/schemas/entity.schema.json +13 -0
- package/dist/universal/.agents/skills/citable/schemas/observation.schema.json +1 -1
- package/dist/universal/.agents/skills/citable/schemas/page.schema.json +3 -0
- package/dist/universal/.agents/skills/citable/schemas/regional-network-import.schema.json +55 -0
- package/dist/universal/.agents/skills/citable/schemas/schedule.schema.json +1 -1
- package/dist/universal/.agents/skills/citable/schemas/source-identity-chain.schema.json +83 -0
- package/dist/universal/.agents/skills/citable/scripts/README.md +3 -1
- package/dist/universal/.claude/agents/citable/citable-auditor.md +64 -0
- package/dist/universal/.claude/agents/citable/citable-semantic-reviewer.md +66 -0
- package/dist/universal/.claude/agents/citable/manifest.json +12 -0
- package/dist/universal/.claude/skills/citable/SKILL.md +22 -8
- package/dist/universal/.claude/skills/citable/VERSION +1 -1
- package/dist/universal/.claude/skills/citable/agents/citable-auditor.md +64 -0
- package/dist/universal/.claude/skills/citable/agents/citable-semantic-reviewer.md +66 -0
- package/dist/universal/.claude/skills/citable/commands/README.md +4 -1
- package/dist/universal/.claude/skills/citable/commands/action-findings.md +9 -1
- package/dist/universal/.claude/skills/citable/commands/observe-and-act.md +62 -8
- package/dist/universal/.claude/skills/citable/commands/plan-audit.md +35 -0
- package/dist/universal/.claude/skills/citable/commands/reporting.md +57 -0
- package/dist/universal/.claude/skills/citable/manifest.json +34 -16
- package/dist/universal/.claude/skills/citable/schemas/action-plan.schema.json +13 -0
- package/dist/universal/.claude/skills/citable/schemas/browser-evidence-plan.schema.json +14 -0
- package/dist/universal/.claude/skills/citable/schemas/canonical-index-import.schema.json +30 -0
- package/dist/universal/.claude/skills/citable/schemas/common.defs.json +12 -1
- package/dist/universal/.claude/skills/citable/schemas/corroboration-import.schema.json +48 -0
- package/dist/universal/.claude/skills/citable/schemas/entity-graph.schema.json +75 -0
- package/dist/universal/.claude/skills/citable/schemas/entity.schema.json +13 -0
- package/dist/universal/.claude/skills/citable/schemas/observation.schema.json +1 -1
- package/dist/universal/.claude/skills/citable/schemas/page.schema.json +3 -0
- package/dist/universal/.claude/skills/citable/schemas/regional-network-import.schema.json +55 -0
- package/dist/universal/.claude/skills/citable/schemas/schedule.schema.json +1 -1
- package/dist/universal/.claude/skills/citable/schemas/source-identity-chain.schema.json +83 -0
- package/dist/universal/.claude/skills/citable/scripts/README.md +3 -1
- package/dist/universal/.cursor/skills/citable/SKILL.md +22 -8
- package/dist/universal/.cursor/skills/citable/VERSION +1 -1
- package/dist/universal/.cursor/skills/citable/agents/citable-auditor.md +64 -0
- package/dist/universal/.cursor/skills/citable/agents/citable-semantic-reviewer.md +66 -0
- package/dist/universal/.cursor/skills/citable/commands/README.md +4 -1
- package/dist/universal/.cursor/skills/citable/commands/action-findings.md +9 -1
- package/dist/universal/.cursor/skills/citable/commands/observe-and-act.md +62 -8
- package/dist/universal/.cursor/skills/citable/commands/plan-audit.md +35 -0
- package/dist/universal/.cursor/skills/citable/commands/reporting.md +57 -0
- package/dist/universal/.cursor/skills/citable/manifest.json +29 -16
- package/dist/universal/.cursor/skills/citable/schemas/action-plan.schema.json +13 -0
- package/dist/universal/.cursor/skills/citable/schemas/browser-evidence-plan.schema.json +14 -0
- package/dist/universal/.cursor/skills/citable/schemas/canonical-index-import.schema.json +30 -0
- package/dist/universal/.cursor/skills/citable/schemas/common.defs.json +12 -1
- package/dist/universal/.cursor/skills/citable/schemas/corroboration-import.schema.json +48 -0
- package/dist/universal/.cursor/skills/citable/schemas/entity-graph.schema.json +75 -0
- package/dist/universal/.cursor/skills/citable/schemas/entity.schema.json +13 -0
- package/dist/universal/.cursor/skills/citable/schemas/observation.schema.json +1 -1
- package/dist/universal/.cursor/skills/citable/schemas/page.schema.json +3 -0
- package/dist/universal/.cursor/skills/citable/schemas/regional-network-import.schema.json +55 -0
- package/dist/universal/.cursor/skills/citable/schemas/schedule.schema.json +1 -1
- package/dist/universal/.cursor/skills/citable/schemas/source-identity-chain.schema.json +83 -0
- package/dist/universal/.cursor/skills/citable/scripts/README.md +3 -1
- package/dist/universal/.gemini/skills/citable/SKILL.md +22 -8
- package/dist/universal/.gemini/skills/citable/VERSION +1 -1
- package/dist/universal/.gemini/skills/citable/agents/citable-auditor.md +64 -0
- package/dist/universal/.gemini/skills/citable/agents/citable-semantic-reviewer.md +66 -0
- package/dist/universal/.gemini/skills/citable/commands/README.md +4 -1
- package/dist/universal/.gemini/skills/citable/commands/action-findings.md +9 -1
- package/dist/universal/.gemini/skills/citable/commands/observe-and-act.md +62 -8
- package/dist/universal/.gemini/skills/citable/commands/plan-audit.md +35 -0
- package/dist/universal/.gemini/skills/citable/commands/reporting.md +57 -0
- package/dist/universal/.gemini/skills/citable/manifest.json +29 -16
- package/dist/universal/.gemini/skills/citable/schemas/action-plan.schema.json +13 -0
- package/dist/universal/.gemini/skills/citable/schemas/browser-evidence-plan.schema.json +14 -0
- package/dist/universal/.gemini/skills/citable/schemas/canonical-index-import.schema.json +30 -0
- package/dist/universal/.gemini/skills/citable/schemas/common.defs.json +12 -1
- package/dist/universal/.gemini/skills/citable/schemas/corroboration-import.schema.json +48 -0
- package/dist/universal/.gemini/skills/citable/schemas/entity-graph.schema.json +75 -0
- package/dist/universal/.gemini/skills/citable/schemas/entity.schema.json +13 -0
- package/dist/universal/.gemini/skills/citable/schemas/observation.schema.json +1 -1
- package/dist/universal/.gemini/skills/citable/schemas/page.schema.json +3 -0
- package/dist/universal/.gemini/skills/citable/schemas/regional-network-import.schema.json +55 -0
- package/dist/universal/.gemini/skills/citable/schemas/schedule.schema.json +1 -1
- package/dist/universal/.gemini/skills/citable/schemas/source-identity-chain.schema.json +83 -0
- package/dist/universal/.gemini/skills/citable/scripts/README.md +3 -1
- package/dist/universal/.github/skills/citable/SKILL.md +22 -8
- package/dist/universal/.github/skills/citable/VERSION +1 -1
- package/dist/universal/.github/skills/citable/agents/citable-auditor.md +64 -0
- package/dist/universal/.github/skills/citable/agents/citable-semantic-reviewer.md +66 -0
- package/dist/universal/.github/skills/citable/commands/README.md +4 -1
- package/dist/universal/.github/skills/citable/commands/action-findings.md +9 -1
- package/dist/universal/.github/skills/citable/commands/observe-and-act.md +62 -8
- package/dist/universal/.github/skills/citable/commands/plan-audit.md +35 -0
- package/dist/universal/.github/skills/citable/commands/reporting.md +57 -0
- package/dist/universal/.github/skills/citable/manifest.json +29 -16
- package/dist/universal/.github/skills/citable/schemas/action-plan.schema.json +13 -0
- package/dist/universal/.github/skills/citable/schemas/browser-evidence-plan.schema.json +14 -0
- package/dist/universal/.github/skills/citable/schemas/canonical-index-import.schema.json +30 -0
- package/dist/universal/.github/skills/citable/schemas/common.defs.json +12 -1
- package/dist/universal/.github/skills/citable/schemas/corroboration-import.schema.json +48 -0
- package/dist/universal/.github/skills/citable/schemas/entity-graph.schema.json +75 -0
- package/dist/universal/.github/skills/citable/schemas/entity.schema.json +13 -0
- package/dist/universal/.github/skills/citable/schemas/observation.schema.json +1 -1
- package/dist/universal/.github/skills/citable/schemas/page.schema.json +3 -0
- package/dist/universal/.github/skills/citable/schemas/regional-network-import.schema.json +55 -0
- package/dist/universal/.github/skills/citable/schemas/schedule.schema.json +1 -1
- package/dist/universal/.github/skills/citable/schemas/source-identity-chain.schema.json +83 -0
- package/dist/universal/.github/skills/citable/scripts/README.md +3 -1
- package/dist/universal/.kiro/skills/citable/SKILL.md +22 -8
- package/dist/universal/.kiro/skills/citable/VERSION +1 -1
- package/dist/universal/.kiro/skills/citable/agents/citable-auditor.md +64 -0
- package/dist/universal/.kiro/skills/citable/agents/citable-semantic-reviewer.md +66 -0
- package/dist/universal/.kiro/skills/citable/commands/README.md +4 -1
- package/dist/universal/.kiro/skills/citable/commands/action-findings.md +9 -1
- package/dist/universal/.kiro/skills/citable/commands/observe-and-act.md +62 -8
- package/dist/universal/.kiro/skills/citable/commands/plan-audit.md +35 -0
- package/dist/universal/.kiro/skills/citable/commands/reporting.md +57 -0
- package/dist/universal/.kiro/skills/citable/manifest.json +29 -16
- package/dist/universal/.kiro/skills/citable/schemas/action-plan.schema.json +13 -0
- package/dist/universal/.kiro/skills/citable/schemas/browser-evidence-plan.schema.json +14 -0
- package/dist/universal/.kiro/skills/citable/schemas/canonical-index-import.schema.json +30 -0
- package/dist/universal/.kiro/skills/citable/schemas/common.defs.json +12 -1
- package/dist/universal/.kiro/skills/citable/schemas/corroboration-import.schema.json +48 -0
- package/dist/universal/.kiro/skills/citable/schemas/entity-graph.schema.json +75 -0
- package/dist/universal/.kiro/skills/citable/schemas/entity.schema.json +13 -0
- package/dist/universal/.kiro/skills/citable/schemas/observation.schema.json +1 -1
- package/dist/universal/.kiro/skills/citable/schemas/page.schema.json +3 -0
- package/dist/universal/.kiro/skills/citable/schemas/regional-network-import.schema.json +55 -0
- package/dist/universal/.kiro/skills/citable/schemas/schedule.schema.json +1 -1
- package/dist/universal/.kiro/skills/citable/schemas/source-identity-chain.schema.json +83 -0
- package/dist/universal/.kiro/skills/citable/scripts/README.md +3 -1
- package/dist/universal/.opencode/skills/citable/SKILL.md +22 -8
- package/dist/universal/.opencode/skills/citable/VERSION +1 -1
- package/dist/universal/.opencode/skills/citable/agents/citable-auditor.md +64 -0
- package/dist/universal/.opencode/skills/citable/agents/citable-semantic-reviewer.md +66 -0
- package/dist/universal/.opencode/skills/citable/commands/README.md +4 -1
- package/dist/universal/.opencode/skills/citable/commands/action-findings.md +9 -1
- package/dist/universal/.opencode/skills/citable/commands/observe-and-act.md +62 -8
- package/dist/universal/.opencode/skills/citable/commands/plan-audit.md +35 -0
- package/dist/universal/.opencode/skills/citable/commands/reporting.md +57 -0
- package/dist/universal/.opencode/skills/citable/manifest.json +29 -16
- package/dist/universal/.opencode/skills/citable/schemas/action-plan.schema.json +13 -0
- package/dist/universal/.opencode/skills/citable/schemas/browser-evidence-plan.schema.json +14 -0
- package/dist/universal/.opencode/skills/citable/schemas/canonical-index-import.schema.json +30 -0
- package/dist/universal/.opencode/skills/citable/schemas/common.defs.json +12 -1
- package/dist/universal/.opencode/skills/citable/schemas/corroboration-import.schema.json +48 -0
- package/dist/universal/.opencode/skills/citable/schemas/entity-graph.schema.json +75 -0
- package/dist/universal/.opencode/skills/citable/schemas/entity.schema.json +13 -0
- package/dist/universal/.opencode/skills/citable/schemas/observation.schema.json +1 -1
- package/dist/universal/.opencode/skills/citable/schemas/page.schema.json +3 -0
- package/dist/universal/.opencode/skills/citable/schemas/regional-network-import.schema.json +55 -0
- package/dist/universal/.opencode/skills/citable/schemas/schedule.schema.json +1 -1
- package/dist/universal/.opencode/skills/citable/schemas/source-identity-chain.schema.json +83 -0
- package/dist/universal/.opencode/skills/citable/scripts/README.md +3 -1
- package/dist/universal/.pi/agent/skills/citable/SKILL.md +22 -8
- package/dist/universal/.pi/agent/skills/citable/VERSION +1 -1
- package/dist/universal/.pi/agent/skills/citable/agents/citable-auditor.md +64 -0
- package/dist/universal/.pi/agent/skills/citable/agents/citable-semantic-reviewer.md +66 -0
- package/dist/universal/.pi/agent/skills/citable/commands/README.md +4 -1
- package/dist/universal/.pi/agent/skills/citable/commands/action-findings.md +9 -1
- package/dist/universal/.pi/agent/skills/citable/commands/observe-and-act.md +62 -8
- package/dist/universal/.pi/agent/skills/citable/commands/plan-audit.md +35 -0
- package/dist/universal/.pi/agent/skills/citable/commands/reporting.md +57 -0
- package/dist/universal/.pi/agent/skills/citable/manifest.json +29 -16
- package/dist/universal/.pi/agent/skills/citable/schemas/action-plan.schema.json +13 -0
- package/dist/universal/.pi/agent/skills/citable/schemas/browser-evidence-plan.schema.json +14 -0
- package/dist/universal/.pi/agent/skills/citable/schemas/canonical-index-import.schema.json +30 -0
- package/dist/universal/.pi/agent/skills/citable/schemas/common.defs.json +12 -1
- package/dist/universal/.pi/agent/skills/citable/schemas/corroboration-import.schema.json +48 -0
- package/dist/universal/.pi/agent/skills/citable/schemas/entity-graph.schema.json +75 -0
- package/dist/universal/.pi/agent/skills/citable/schemas/entity.schema.json +13 -0
- package/dist/universal/.pi/agent/skills/citable/schemas/observation.schema.json +1 -1
- package/dist/universal/.pi/agent/skills/citable/schemas/page.schema.json +3 -0
- package/dist/universal/.pi/agent/skills/citable/schemas/regional-network-import.schema.json +55 -0
- package/dist/universal/.pi/agent/skills/citable/schemas/schedule.schema.json +1 -1
- package/dist/universal/.pi/agent/skills/citable/schemas/source-identity-chain.schema.json +83 -0
- package/dist/universal/.pi/agent/skills/citable/scripts/README.md +3 -1
- package/dist/universal/.qoder/skills/citable/SKILL.md +22 -8
- package/dist/universal/.qoder/skills/citable/VERSION +1 -1
- package/dist/universal/.qoder/skills/citable/agents/citable-auditor.md +64 -0
- package/dist/universal/.qoder/skills/citable/agents/citable-semantic-reviewer.md +66 -0
- package/dist/universal/.qoder/skills/citable/commands/README.md +4 -1
- package/dist/universal/.qoder/skills/citable/commands/action-findings.md +9 -1
- package/dist/universal/.qoder/skills/citable/commands/observe-and-act.md +62 -8
- package/dist/universal/.qoder/skills/citable/commands/plan-audit.md +35 -0
- package/dist/universal/.qoder/skills/citable/commands/reporting.md +57 -0
- package/dist/universal/.qoder/skills/citable/manifest.json +29 -16
- package/dist/universal/.qoder/skills/citable/schemas/action-plan.schema.json +13 -0
- package/dist/universal/.qoder/skills/citable/schemas/browser-evidence-plan.schema.json +14 -0
- package/dist/universal/.qoder/skills/citable/schemas/canonical-index-import.schema.json +30 -0
- package/dist/universal/.qoder/skills/citable/schemas/common.defs.json +12 -1
- package/dist/universal/.qoder/skills/citable/schemas/corroboration-import.schema.json +48 -0
- package/dist/universal/.qoder/skills/citable/schemas/entity-graph.schema.json +75 -0
- package/dist/universal/.qoder/skills/citable/schemas/entity.schema.json +13 -0
- package/dist/universal/.qoder/skills/citable/schemas/observation.schema.json +1 -1
- package/dist/universal/.qoder/skills/citable/schemas/page.schema.json +3 -0
- package/dist/universal/.qoder/skills/citable/schemas/regional-network-import.schema.json +55 -0
- package/dist/universal/.qoder/skills/citable/schemas/schedule.schema.json +1 -1
- package/dist/universal/.qoder/skills/citable/schemas/source-identity-chain.schema.json +83 -0
- package/dist/universal/.qoder/skills/citable/scripts/README.md +3 -1
- package/dist/universal/.rovodev/skills/citable/SKILL.md +22 -8
- package/dist/universal/.rovodev/skills/citable/VERSION +1 -1
- package/dist/universal/.rovodev/skills/citable/agents/citable-auditor.md +64 -0
- package/dist/universal/.rovodev/skills/citable/agents/citable-semantic-reviewer.md +66 -0
- package/dist/universal/.rovodev/skills/citable/commands/README.md +4 -1
- package/dist/universal/.rovodev/skills/citable/commands/action-findings.md +9 -1
- package/dist/universal/.rovodev/skills/citable/commands/observe-and-act.md +62 -8
- package/dist/universal/.rovodev/skills/citable/commands/plan-audit.md +35 -0
- package/dist/universal/.rovodev/skills/citable/commands/reporting.md +57 -0
- package/dist/universal/.rovodev/skills/citable/manifest.json +29 -16
- package/dist/universal/.rovodev/skills/citable/schemas/action-plan.schema.json +13 -0
- package/dist/universal/.rovodev/skills/citable/schemas/browser-evidence-plan.schema.json +14 -0
- package/dist/universal/.rovodev/skills/citable/schemas/canonical-index-import.schema.json +30 -0
- package/dist/universal/.rovodev/skills/citable/schemas/common.defs.json +12 -1
- package/dist/universal/.rovodev/skills/citable/schemas/corroboration-import.schema.json +48 -0
- package/dist/universal/.rovodev/skills/citable/schemas/entity-graph.schema.json +75 -0
- package/dist/universal/.rovodev/skills/citable/schemas/entity.schema.json +13 -0
- package/dist/universal/.rovodev/skills/citable/schemas/observation.schema.json +1 -1
- package/dist/universal/.rovodev/skills/citable/schemas/page.schema.json +3 -0
- package/dist/universal/.rovodev/skills/citable/schemas/regional-network-import.schema.json +55 -0
- package/dist/universal/.rovodev/skills/citable/schemas/schedule.schema.json +1 -1
- package/dist/universal/.rovodev/skills/citable/schemas/source-identity-chain.schema.json +83 -0
- package/dist/universal/.rovodev/skills/citable/scripts/README.md +3 -1
- package/dist/universal/.trae/skills/citable/SKILL.md +22 -8
- package/dist/universal/.trae/skills/citable/VERSION +1 -1
- package/dist/universal/.trae/skills/citable/agents/citable-auditor.md +64 -0
- package/dist/universal/.trae/skills/citable/agents/citable-semantic-reviewer.md +66 -0
- package/dist/universal/.trae/skills/citable/commands/README.md +4 -1
- package/dist/universal/.trae/skills/citable/commands/action-findings.md +9 -1
- package/dist/universal/.trae/skills/citable/commands/observe-and-act.md +62 -8
- package/dist/universal/.trae/skills/citable/commands/plan-audit.md +35 -0
- package/dist/universal/.trae/skills/citable/commands/reporting.md +57 -0
- package/dist/universal/.trae/skills/citable/manifest.json +29 -16
- package/dist/universal/.trae/skills/citable/schemas/action-plan.schema.json +13 -0
- package/dist/universal/.trae/skills/citable/schemas/browser-evidence-plan.schema.json +14 -0
- package/dist/universal/.trae/skills/citable/schemas/canonical-index-import.schema.json +30 -0
- package/dist/universal/.trae/skills/citable/schemas/common.defs.json +12 -1
- package/dist/universal/.trae/skills/citable/schemas/corroboration-import.schema.json +48 -0
- package/dist/universal/.trae/skills/citable/schemas/entity-graph.schema.json +75 -0
- package/dist/universal/.trae/skills/citable/schemas/entity.schema.json +13 -0
- package/dist/universal/.trae/skills/citable/schemas/observation.schema.json +1 -1
- package/dist/universal/.trae/skills/citable/schemas/page.schema.json +3 -0
- package/dist/universal/.trae/skills/citable/schemas/regional-network-import.schema.json +55 -0
- package/dist/universal/.trae/skills/citable/schemas/schedule.schema.json +1 -1
- package/dist/universal/.trae/skills/citable/schemas/source-identity-chain.schema.json +83 -0
- package/dist/universal/.trae/skills/citable/scripts/README.md +3 -1
- package/dist/universal/.trae-cn/skills/citable/SKILL.md +22 -8
- package/dist/universal/.trae-cn/skills/citable/VERSION +1 -1
- package/dist/universal/.trae-cn/skills/citable/agents/citable-auditor.md +64 -0
- package/dist/universal/.trae-cn/skills/citable/agents/citable-semantic-reviewer.md +66 -0
- package/dist/universal/.trae-cn/skills/citable/commands/README.md +4 -1
- package/dist/universal/.trae-cn/skills/citable/commands/action-findings.md +9 -1
- package/dist/universal/.trae-cn/skills/citable/commands/observe-and-act.md +62 -8
- package/dist/universal/.trae-cn/skills/citable/commands/plan-audit.md +35 -0
- package/dist/universal/.trae-cn/skills/citable/commands/reporting.md +57 -0
- package/dist/universal/.trae-cn/skills/citable/manifest.json +29 -16
- package/dist/universal/.trae-cn/skills/citable/schemas/action-plan.schema.json +13 -0
- package/dist/universal/.trae-cn/skills/citable/schemas/browser-evidence-plan.schema.json +14 -0
- package/dist/universal/.trae-cn/skills/citable/schemas/canonical-index-import.schema.json +30 -0
- package/dist/universal/.trae-cn/skills/citable/schemas/common.defs.json +12 -1
- package/dist/universal/.trae-cn/skills/citable/schemas/corroboration-import.schema.json +48 -0
- package/dist/universal/.trae-cn/skills/citable/schemas/entity-graph.schema.json +75 -0
- package/dist/universal/.trae-cn/skills/citable/schemas/entity.schema.json +13 -0
- package/dist/universal/.trae-cn/skills/citable/schemas/observation.schema.json +1 -1
- package/dist/universal/.trae-cn/skills/citable/schemas/page.schema.json +3 -0
- package/dist/universal/.trae-cn/skills/citable/schemas/regional-network-import.schema.json +55 -0
- package/dist/universal/.trae-cn/skills/citable/schemas/schedule.schema.json +1 -1
- package/dist/universal/.trae-cn/skills/citable/schemas/source-identity-chain.schema.json +83 -0
- package/dist/universal/.trae-cn/skills/citable/scripts/README.md +3 -1
- package/dist/universal/manifest.json +306 -195
- package/package.json +1 -1
- package/schemas/action-plan.schema.json +13 -0
- package/schemas/browser-evidence-plan.schema.json +14 -0
- package/schemas/canonical-index-import.schema.json +30 -0
- package/schemas/common.defs.json +12 -1
- package/schemas/corroboration-import.schema.json +48 -0
- package/schemas/entity-graph.schema.json +75 -0
- package/schemas/entity.schema.json +13 -0
- package/schemas/observation.schema.json +1 -1
- package/schemas/page.schema.json +3 -0
- package/schemas/regional-network-import.schema.json +55 -0
- package/schemas/schedule.schema.json +1 -1
- package/schemas/source-identity-chain.schema.json +83 -0
- package/scripts/build-dist.js +36 -3
- package/skill/SKILL.md +22 -8
- package/skill/agents/citable-auditor.md +64 -0
- package/skill/agents/citable-semantic-reviewer.md +66 -0
- package/skill/commands/README.md +4 -1
- package/skill/commands/action-findings.md +9 -1
- package/skill/commands/observe-and-act.md +62 -8
- package/skill/commands/plan-audit.md +35 -0
- package/skill/commands/reporting.md +57 -0
- package/src/cli/index.js +115 -15
- package/src/commands/actionPlan.js +12 -1
- package/src/commands/audit.js +4 -0
- package/src/commands/connect.js +29 -0
- package/src/commands/context.js +15 -0
- package/src/commands/delivery.js +86 -2
- package/src/commands/demo.js +36 -0
- package/src/commands/fixtures/demo/.citable/claims.yaml +27 -0
- package/src/commands/fixtures/demo/.citable/competitors.yaml +3 -0
- package/src/commands/fixtures/demo/.citable/config.yaml +18 -0
- package/src/commands/fixtures/demo/.citable/crawlers.yaml +30 -0
- package/src/commands/fixtures/demo/.citable/entities.yaml +28 -0
- package/src/commands/fixtures/demo/.citable/evidence.yaml +19 -0
- package/src/commands/fixtures/demo/.citable/experiments.yaml +3 -0
- package/src/commands/fixtures/demo/.citable/pages.yaml +54 -0
- package/src/commands/fixtures/demo/.citable/prompts.yaml +11 -0
- package/src/commands/fixtures/demo/.citable/queries.yaml +10 -0
- package/src/commands/fixtures/demo/site/index.html +12 -0
- package/src/commands/fixtures/demo/site/learn/execution-governance/index.html +14 -0
- package/src/commands/fixtures/demo/site/pricing/index.html +10 -0
- package/src/commands/fixtures/demo/site/products/gatekeeper/index.html +15 -0
- package/src/commands/fixtures/demo/site/robots.txt +7 -0
- package/src/commands/fixtures/demo/site/sitemap.xml +7 -0
- package/src/commands/init.js +5 -2
- package/src/commands/monitor.js +35 -0
- package/src/commands/observe.js +315 -15
- package/src/commands/planAudit.js +151 -0
- package/src/commands/reportDashboard.js +21 -0
- package/src/commands/reportShareOfVoice.js +60 -0
- package/src/commands/schemaCmd.js +5 -1
- package/src/commands/substantiate.js +22 -10
- package/src/connectors/http.js +6 -3
- package/src/connectors/index.js +3 -1
- package/src/connectors/webflow.js +224 -0
- package/src/connectors/wordpress.js +226 -0
- package/src/crawler/fetch.js +34 -6
- package/src/detectors/geoReco.js +101 -0
- package/src/extractor/page.js +17 -0
- package/src/installer/index.js +282 -28
- package/src/installer/providers.js +11 -0
- package/src/monitoring/alertDelivery.js +98 -0
- package/src/observations/browserJourney.js +15 -7
- package/src/observations/claimDiff.js +83 -0
- package/src/observations/common.js +25 -0
- package/src/observations/entityGraph.js +89 -0
- package/src/observations/renderParity.js +49 -0
- package/src/observations/sourceIdentity.js +62 -0
- package/src/observations/stance.js +261 -0
- package/src/registries/seed.js +95 -0
- package/src/registries/seeds/saas-pricing/claims.yaml +18 -0
- package/src/registries/seeds/saas-pricing/entities.yaml +20 -0
- package/src/registries/seeds/saas-pricing/seed.json +6 -0
- package/src/reporting/dashboard.js +254 -0
- package/src/reporting/shareOfVoice.js +362 -0
- package/src/shared/checklist.js +19 -0
package/src/commands/observe.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { buildContext } from './context.js';
|
|
2
2
|
import { envelope, observationRun, readInput } from '../observations/common.js';
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
3
|
+
import { buildClaimDiffContext, diffAssertion } from '../observations/claimDiff.js';
|
|
4
|
+
import { readYaml, sha256 } from '../shared/io.js';
|
|
5
|
+
import { fetchUrl, handlePublicBrowserRoute, validatePublicUrl } from '../crawler/fetch.js';
|
|
5
6
|
import fs from 'node:fs';
|
|
6
7
|
import path from 'node:path';
|
|
7
8
|
import { parse as parseCsv } from 'csv-parse/sync';
|
|
@@ -11,6 +12,7 @@ import { observeMedia } from '../observations/media.js';
|
|
|
11
12
|
import { parse as parseHtml } from 'node-html-parser';
|
|
12
13
|
import { verifyManifestIntegrity } from '../release/governance.js';
|
|
13
14
|
import { observeBrowserPlan } from '../observations/browserJourney.js';
|
|
15
|
+
import { observeStance } from '../observations/stance.js';
|
|
14
16
|
|
|
15
17
|
const originOf = (value) => { try { return new URL(value).origin; } catch { return null; } };
|
|
16
18
|
const words = (text) => String(text || '').trim().split(/\s+/).filter(Boolean);
|
|
@@ -28,6 +30,20 @@ function canonicalReview(raw, targetOrigin) {
|
|
|
28
30
|
const url = typeof citation === 'string' ? citation : citation.url;
|
|
29
31
|
const canonical = typeof citation === 'object' ? citation.canonical_url || url : url;
|
|
30
32
|
const supports = typeof citation === 'object' ? citation.support_status || 'review_required' : 'review_required';
|
|
33
|
+
const sourceProfile = typeof citation === 'object' ? citation.source_profile || null : null;
|
|
34
|
+
if (sourceProfile) {
|
|
35
|
+
const formats = new Set(['article', 'documentation', 'product', 'landing_page', 'comparison', 'forum', 'video', 'pdf', 'other']);
|
|
36
|
+
const allowedKeys = new Set(['content_format', 'word_count', 'heading_count', 'freshness_date', 'evidence_features']);
|
|
37
|
+
if (Object.keys(sourceProfile).some((key) => !allowedKeys.has(key))
|
|
38
|
+
|| !formats.has(sourceProfile.content_format)
|
|
39
|
+
|| !Number.isInteger(sourceProfile.word_count) || sourceProfile.word_count < 0
|
|
40
|
+
|| !Number.isInteger(sourceProfile.heading_count) || sourceProfile.heading_count < 0
|
|
41
|
+
|| !strictDate(sourceProfile.freshness_date)
|
|
42
|
+
|| !Array.isArray(sourceProfile.evidence_features)
|
|
43
|
+
|| sourceProfile.evidence_features.some((item) => typeof item !== 'string' || !item.trim())) {
|
|
44
|
+
throw new Error(`citation ${index + 1} source_profile violates the declared competitive profile contract`);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
31
47
|
return {
|
|
32
48
|
citation_url: url, canonical_url: canonical, citation_order: index + 1,
|
|
33
49
|
first_party: Boolean(targetOrigin && originOf(canonical) === targetOrigin),
|
|
@@ -35,6 +51,7 @@ function canonicalReview(raw, targetOrigin) {
|
|
|
35
51
|
answer_claim: typeof citation === 'object' ? citation.answer_claim || null : null,
|
|
36
52
|
source_passage: typeof citation === 'object' ? citation.source_passage || null : null,
|
|
37
53
|
reviewer: typeof citation === 'object' ? citation.reviewer || null : null,
|
|
54
|
+
source_profile: sourceProfile,
|
|
38
55
|
};
|
|
39
56
|
});
|
|
40
57
|
}
|
|
@@ -42,6 +59,7 @@ function canonicalReview(raw, targetOrigin) {
|
|
|
42
59
|
async function observeRender(root, options) {
|
|
43
60
|
if (options.input) return observeBrowserPlan(root, options);
|
|
44
61
|
if (!options.target || !/^https?:\/\//.test(options.target)) throw new Error('render requires --target <http(s) URL>');
|
|
62
|
+
await validatePublicUrl(options.target, options.lookup ? { lookup: options.lookup } : {});
|
|
45
63
|
const profileNames = ['desktop', 'mobile', 'javascript_disabled'];
|
|
46
64
|
let reused = [], previousRaw = null;
|
|
47
65
|
if (options.resumeRun) {
|
|
@@ -66,6 +84,7 @@ async function observeRender(root, options) {
|
|
|
66
84
|
capture = async (name, viewport, { isMobile = false, javaScriptEnabled = true } = {}) => {
|
|
67
85
|
const context = await browser.newContext({ viewport, isMobile, javaScriptEnabled });
|
|
68
86
|
try {
|
|
87
|
+
await context.route('**/*', (route) => handlePublicBrowserRoute(route, options.lookup ? { lookup: options.lookup } : {}));
|
|
69
88
|
const page = await context.newPage();
|
|
70
89
|
const failures = [];
|
|
71
90
|
page.on('requestfailed', (request) => failures.push({ url: request.url(), error: request.failure()?.errorText || 'unknown' }));
|
|
@@ -130,11 +149,12 @@ async function observeCitations(root, options) {
|
|
|
130
149
|
if (options.endpoint) {
|
|
131
150
|
const prompts = input.value.prompts || (Array.isArray(input.value) ? input.value : []);
|
|
132
151
|
if (!prompts.length) throw new Error('citation runner input must contain a prompts array');
|
|
133
|
-
if (!/^https:\/\//.test(options.endpoint)
|
|
152
|
+
if (!/^https:\/\//.test(options.endpoint)) throw new Error('citation adapter endpoint must use HTTPS');
|
|
153
|
+
await validatePublicUrl(options.endpoint, options.lookup ? { lookup: options.lookup } : {});
|
|
134
154
|
const repeat = Number.isInteger(options.repeat) && options.repeat > 0 && options.repeat <= 20 ? options.repeat : 3;
|
|
135
155
|
rows = [];
|
|
136
156
|
for (const prompt of prompts) for (let runIndex = 1; runIndex <= repeat; runIndex++) {
|
|
137
|
-
const response = await fetch(options.endpoint, { method: 'POST', headers: { 'content-type': 'application/json', ...(options.accessToken ? { authorization: `Bearer ${options.accessToken}` } : {}) }, body: JSON.stringify({ prompt_id: prompt.prompt_id, prompt_text: prompt.prompt_text, locale: prompt.locale || input.value.locale || 'en-US', run_index: runIndex, runs_in_series: repeat }) });
|
|
157
|
+
const response = await (options.adapterFetch || globalThis.fetch)(options.endpoint, { method: 'POST', redirect: 'error', headers: { 'content-type': 'application/json', ...(options.accessToken ? { authorization: `Bearer ${options.accessToken}` } : {}) }, body: JSON.stringify({ prompt_id: prompt.prompt_id, prompt_text: prompt.prompt_text, locale: prompt.locale || input.value.locale || 'en-US', run_index: runIndex, runs_in_series: repeat }) });
|
|
138
158
|
const rawResponse = await response.text();
|
|
139
159
|
if (!response.ok) throw new Error(`citation adapter returned ${response.status}: ${rawResponse.slice(0, 300)}`);
|
|
140
160
|
const result = JSON.parse(rawResponse);
|
|
@@ -143,14 +163,23 @@ async function observeCitations(root, options) {
|
|
|
143
163
|
method = 'live_api';
|
|
144
164
|
}
|
|
145
165
|
const targetOrigin = options.target ? originOf(options.target) : input.value.target_origin || null;
|
|
166
|
+
// Registries-only context (no `target` passed — this never fetches or crawls); degrades
|
|
167
|
+
// gracefully to empty registries (and therefore all-`unsupported` diffs) when no
|
|
168
|
+
// `.citable/` exists yet, per loadRegistries()'s missing-file-loads-as-empty contract.
|
|
169
|
+
const diffCtx = buildClaimDiffContext(root, options.refDate);
|
|
146
170
|
const observations = [];
|
|
147
171
|
for (const row of rows) {
|
|
148
|
-
const reviews = canonicalReview(row, targetOrigin)
|
|
172
|
+
const reviews = canonicalReview(row, targetOrigin).map((review) => ({
|
|
173
|
+
...review,
|
|
174
|
+
claim_diff: diffAssertion(review.answer_claim, review.canonical_url, diffCtx),
|
|
175
|
+
}));
|
|
176
|
+
const diffCounts = { confirmed: 0, contradicted: 0, stale: 0, unsupported: 0, not_checked: 0 };
|
|
177
|
+
for (const review of reviews) diffCounts[review.claim_diff?.status ?? 'not_checked']++;
|
|
149
178
|
observations.push(envelope('citation', {
|
|
150
179
|
prompt_id: row.prompt_id, prompt_text: row.prompt_text, provider: row.provider || row.engine,
|
|
151
180
|
product_mode: row.product_mode || row.interface || 'unknown', locale: row.locale || 'unknown',
|
|
152
181
|
answer_text: row.answer_text || '', citations: reviews, run_index: row.run_index || 1,
|
|
153
|
-
property_cited: reviews.some((r) => r.first_party),
|
|
182
|
+
property_cited: reviews.some((r) => r.first_party), claim_diff_summary: diffCounts,
|
|
154
183
|
}, { method, source: options.endpoint || input.file, raw: JSON.stringify(row) }));
|
|
155
184
|
for (const review of reviews) observations.push(envelope('citation_review', review, {
|
|
156
185
|
method: review.reviewer ? 'human_review' : 'static_analysis', source: options.endpoint || input.file,
|
|
@@ -159,7 +188,8 @@ async function observeCitations(root, options) {
|
|
|
159
188
|
limitations: review.reviewer ? [] : ['Material support requires a named human reviewer.'],
|
|
160
189
|
}));
|
|
161
190
|
}
|
|
162
|
-
|
|
191
|
+
const claimDiffWarnings = diffCtx.problems.map((p) => `claim-diff registry problem (diff degraded to unsupported where affected): ${p}`);
|
|
192
|
+
return observationRun(root, 'observe citations', input.file, observations, { rawInputs: { citation_results: input.raw }, warnings: claimDiffWarnings });
|
|
163
193
|
}
|
|
164
194
|
|
|
165
195
|
function observeLogs(root, options) {
|
|
@@ -248,24 +278,82 @@ async function observePassages(root, options) {
|
|
|
248
278
|
const ctx = await buildContext(root, options);
|
|
249
279
|
if (!ctx.site) throw new Error('passages requires --target <dir|url>');
|
|
250
280
|
const observations = [];
|
|
281
|
+
const regionPages = new Map();
|
|
282
|
+
for (const page of ctx.site.pages) {
|
|
283
|
+
for (const region of page.structuralRegions || []) {
|
|
284
|
+
const key = sha256(`${region.region_type}\0${region.text}`);
|
|
285
|
+
if (!regionPages.has(key)) regionPages.set(key, new Set());
|
|
286
|
+
regionPages.get(key).add(page.url);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
251
289
|
for (const page of ctx.site.pages) {
|
|
252
290
|
const rawWords = words(page.text).length;
|
|
291
|
+
const pageRecord = ctx.registries.pages?.entries?.find((item) => {
|
|
292
|
+
try { return new URL(item.url, page.url).href === page.url; } catch { return item.url === page.url; }
|
|
293
|
+
});
|
|
294
|
+
const promptMap = new Map((ctx.registries.prompts?.entries || []).map((item) => [item.prompt_id, item]));
|
|
295
|
+
const questionContexts = (pageRecord?.target_prompts || []).map((id) => promptMap.get(id)).filter(Boolean).map((prompt) => ({
|
|
296
|
+
prompt_id: prompt.prompt_id,
|
|
297
|
+
question: prompt.canonical_prompt,
|
|
298
|
+
expected_answer_components: prompt.expected_answer_components || [],
|
|
299
|
+
}));
|
|
300
|
+
const extractionMetrics = {
|
|
301
|
+
raw_bytes: Buffer.byteLength(page.rawHtml),
|
|
302
|
+
extracted_bytes: Buffer.byteLength(page.text),
|
|
303
|
+
raw_visible_tokens: page.rawVisibleWordCount,
|
|
304
|
+
extracted_tokens: rawWords,
|
|
305
|
+
extracted_to_raw_byte_ratio: page.rawHtml.length ? Number((Buffer.byteLength(page.text) / Buffer.byteLength(page.rawHtml)).toFixed(3)) : null,
|
|
306
|
+
extracted_to_raw_visible_token_ratio: page.rawVisibleWordCount ? Number((rawWords / page.rawVisibleWordCount).toFixed(3)) : null,
|
|
307
|
+
};
|
|
308
|
+
const repeatedRegions = (page.structuralRegions || []).map((region) => {
|
|
309
|
+
const regionHash = sha256(`${region.region_type}\0${region.text}`);
|
|
310
|
+
const pageOccurrences = regionPages.get(regionHash)?.size || 1;
|
|
311
|
+
return {
|
|
312
|
+
region_type: region.region_type,
|
|
313
|
+
region_hash: regionHash,
|
|
314
|
+
html_bytes: region.html_bytes,
|
|
315
|
+
token_count: region.token_count,
|
|
316
|
+
page_occurrences: pageOccurrences,
|
|
317
|
+
classification: pageOccurrences >= 2 ? 'repeated_site_region' : 'page_local_region',
|
|
318
|
+
};
|
|
319
|
+
});
|
|
320
|
+
const limitations = ['Repeated structural regions and extraction ratios are descriptive cohort evidence; they do not establish content harm, retrieval impact, or ranking effect.'];
|
|
253
321
|
for (let i = 0; i < page.paragraphs.length; i++) {
|
|
254
322
|
const passage = page.paragraphs.slice(i, i + 3).join(' ');
|
|
255
323
|
const count = words(passage).length;
|
|
256
324
|
if (count < 30) continue;
|
|
257
325
|
const dependencies = [];
|
|
258
326
|
if (/\b(this|that|these|those|it|they|above|below|here)\b/i.test(passage.slice(0, 100))) dependencies.push('possible external referent');
|
|
259
|
-
observations.push(envelope('passage', { url: page.url, passage_index: i, text: passage, word_count: count, independently_extractable: count >= 100 && count <= 300 && dependencies.length === 0, dependencies,
|
|
327
|
+
observations.push(envelope('passage', { url: page.url, passage_contract_version: 1, passage_index: i, text: passage, word_count: count, independently_extractable: count >= 100 && count <= 300 && dependencies.length === 0, dependencies, question_contexts: questionContexts, semantic_review_status: 'review_required', content_to_noise_ratio: extractionMetrics.extracted_to_raw_byte_ratio, extraction_metrics: extractionMetrics, repeated_regions: repeatedRegions }, { method: 'static_analysis', source: page.sourceFile || page.url, raw: passage, confidence: 'medium', limitations: [...limitations, 'Question-to-passage suitability remains review_required and is not inferred from registry linkage.'] }));
|
|
260
328
|
}
|
|
261
|
-
if (!page.paragraphs.length) observations.push(envelope('passage', { url: page.url, word_count: rawWords, independently_extractable: false, dependencies: ['no paragraph passages extracted'] }, { method: 'static_analysis', source: page.sourceFile || page.url, state: 'not_observed', confidence: 'confirmed' }));
|
|
329
|
+
if (!page.paragraphs.length) observations.push(envelope('passage', { url: page.url, passage_contract_version: 1, word_count: rawWords, independently_extractable: false, dependencies: ['no paragraph passages extracted'], question_contexts: questionContexts, semantic_review_status: 'review_required', extraction_metrics: extractionMetrics, repeated_regions: repeatedRegions }, { method: 'static_analysis', source: page.sourceFile || page.url, state: 'not_observed', confidence: 'confirmed', limitations: [...limitations, 'Question-to-passage suitability remains review_required and is not inferred from registry linkage.'] }));
|
|
262
330
|
}
|
|
263
331
|
return observationRun(root, 'observe passages', options.target, observations);
|
|
264
332
|
}
|
|
265
333
|
|
|
334
|
+
function normalizeDateSignal(source, raw) {
|
|
335
|
+
if (raw == null || raw === '') return null;
|
|
336
|
+
const text = String(raw).trim();
|
|
337
|
+
const dateOnly = /^\d{4}-\d{2}-\d{2}$/.test(text);
|
|
338
|
+
const date = new Date(dateOnly ? `${text}T00:00:00Z` : text);
|
|
339
|
+
const valid = !Number.isNaN(date.getTime()) && (!dateOnly || date.toISOString().slice(0, 10) === text);
|
|
340
|
+
return {
|
|
341
|
+
source,
|
|
342
|
+
raw: text,
|
|
343
|
+
normalized_date: valid ? date.toISOString().slice(0, 10) : null,
|
|
344
|
+
valid,
|
|
345
|
+
};
|
|
346
|
+
}
|
|
347
|
+
|
|
266
348
|
async function observeConsensus(root, options) {
|
|
267
349
|
const ctx = await buildContext(root, options);
|
|
268
350
|
if (!ctx.site) throw new Error('consensus requires --target <dir|url>');
|
|
351
|
+
let canonicalInput = null;
|
|
352
|
+
if (options.input) {
|
|
353
|
+
canonicalInput = readInput(options.input);
|
|
354
|
+
const check = validateAgainst('canonical-index-import.schema.json', canonicalInput.value);
|
|
355
|
+
if (!check.valid) throw new Error(`canonical index import violates contract: ${check.errors.join('; ')}`);
|
|
356
|
+
}
|
|
269
357
|
const sitemapRows = ctx.site.sitemaps.flatMap((s) => s.parsed?.urls || []);
|
|
270
358
|
const sitemapUrls = new Set(sitemapRows.map((u) => u.loc));
|
|
271
359
|
const observations = ctx.site.pages.map((page) => {
|
|
@@ -274,10 +362,59 @@ async function observeConsensus(root, options) {
|
|
|
274
362
|
const visibleDate = page.metas['article:modified_time']?.[0] || page.metas['date.modified']?.[0] || page.metas['last-modified']?.[0] || null;
|
|
275
363
|
const signals = { final_url: page.url, html_canonical: declared, open_graph_url: page.ogUrl, sitemap_present: sitemapUrls.has(page.url), last_modified_header: page.headers['last-modified'] || null, sitemap_lastmod: sitemapDate, visible_or_meta_modified: visibleDate };
|
|
276
364
|
const urls = [page.url, declared, page.ogUrl].filter(Boolean);
|
|
277
|
-
const
|
|
278
|
-
|
|
365
|
+
const engineCanonicals = canonicalInput?.value.observations.filter((item) => item.url === page.url).map((item) => ({
|
|
366
|
+
engine: item.engine,
|
|
367
|
+
selected_canonical: item.selected_canonical,
|
|
368
|
+
indexed: item.indexed,
|
|
369
|
+
observed_at: item.observed_at,
|
|
370
|
+
collection_authority: canonicalInput.value.collection_authority,
|
|
371
|
+
authenticity_status: canonicalInput.value.authenticity_status,
|
|
372
|
+
})) || [];
|
|
373
|
+
const dateSignals = [
|
|
374
|
+
normalizeDateSignal('http_last_modified', signals.last_modified_header),
|
|
375
|
+
normalizeDateSignal('sitemap_lastmod', sitemapDate),
|
|
376
|
+
normalizeDateSignal('visible_or_meta_modified', visibleDate),
|
|
377
|
+
].filter(Boolean);
|
|
378
|
+
const validDates = dateSignals.filter((item) => item.valid).map((item) => item.normalized_date);
|
|
379
|
+
const dateConsensus = validDates.length >= 2 ? new Set(validDates).size === 1 : null;
|
|
380
|
+
const prior = ctx.snapshots?.pages?.[page.url] || null;
|
|
381
|
+
const currentHash = ctx.hashPage(page);
|
|
382
|
+
const changed = prior ? prior.contentHash !== currentHash : null;
|
|
383
|
+
const pageRegistry = ctx.registries.pages?.entries?.find((item) => item.url === page.url || item.canonical_url === page.url) || null;
|
|
384
|
+
const contentSnapshot = {
|
|
385
|
+
current_content_hash: currentHash,
|
|
386
|
+
prior_run_id: prior ? ctx.snapshots.run_id || null : null,
|
|
387
|
+
prior_content_hash: prior?.contentHash || null,
|
|
388
|
+
changed_since_snapshot: changed,
|
|
389
|
+
change_observed_after: changed ? ctx.snapshots.taken_at || null : null,
|
|
390
|
+
change_observed_by: changed ? new Date().toISOString() : null,
|
|
391
|
+
exact_change_time_established: false,
|
|
392
|
+
};
|
|
393
|
+
const limitations = ['Engine-selected canonical requires external observations. Snapshot comparison can bound a detected change interval but cannot establish the exact modification time or cause.'];
|
|
394
|
+
if (dateSignals.some((item) => !item.valid)) limitations.push('One or more declared freshness dates could not be parsed and were excluded from consensus.');
|
|
395
|
+
const data = {
|
|
396
|
+
url: page.url,
|
|
397
|
+
signals,
|
|
398
|
+
canonical_consensus: new Set(urls).size <= 1,
|
|
399
|
+
date_signals: dateSignals,
|
|
400
|
+
date_consensus: dateConsensus,
|
|
401
|
+
freshness_assessment: validDates.length < 2 ? 'insufficient_signals' : dateConsensus ? 'aligned_signals' : 'conflicting_signals',
|
|
402
|
+
content_snapshot: contentSnapshot,
|
|
403
|
+
lifecycle_governance: pageRegistry ? {
|
|
404
|
+
page_id: pageRegistry.page_id,
|
|
405
|
+
lifecycle_class: pageRegistry.lifecycle_class || 'unclassified',
|
|
406
|
+
next_review_date: pageRegistry.next_review_date || null,
|
|
407
|
+
} : null,
|
|
408
|
+
engine_selected_canonical: engineCanonicals.length ? engineCanonicals : null,
|
|
409
|
+
canonical_consensus_with_engines: engineCanonicals.length
|
|
410
|
+
? new Set([...urls, ...engineCanonicals.map((item) => item.selected_canonical).filter(Boolean)]).size <= 1
|
|
411
|
+
: null,
|
|
412
|
+
};
|
|
413
|
+
return envelope('canonical_freshness', data, { method: 'static_analysis', source: page.sourceFile || page.url, raw: JSON.stringify({ signals, dateSignals, contentSnapshot }), confidence: 'high', limitations });
|
|
414
|
+
});
|
|
415
|
+
return observationRun(root, 'observe consensus', options.target, observations, {
|
|
416
|
+
rawInputs: canonicalInput ? { canonical_index_export: canonicalInput.raw } : {},
|
|
279
417
|
});
|
|
280
|
-
return observationRun(root, 'observe consensus', options.target, observations);
|
|
281
418
|
}
|
|
282
419
|
|
|
283
420
|
const median = (values) => {
|
|
@@ -374,11 +511,171 @@ async function observePerformance(root, options) {
|
|
|
374
511
|
|
|
375
512
|
function observeCorroboration(root, options) {
|
|
376
513
|
const input = readInput(options.input);
|
|
377
|
-
const
|
|
378
|
-
const
|
|
514
|
+
const document = input.value;
|
|
515
|
+
const check = validateAgainst('corroboration-import.schema.json', document);
|
|
516
|
+
if (!check.valid) throw new Error(`corroboration import violates contract: ${check.errors.join('; ')}`);
|
|
517
|
+
const observations = document.mentions.map((row) => {
|
|
518
|
+
const independent = row.source_control === 'independent' && row.relationship_disclosure === 'none_disclosed';
|
|
519
|
+
const sourceAuthority = independent
|
|
520
|
+
? 'independently_controlled'
|
|
521
|
+
: row.source_control === 'owner_controlled'
|
|
522
|
+
? 'owner_controlled'
|
|
523
|
+
: row.source_control === 'affiliated'
|
|
524
|
+
? 'third_party_commercial'
|
|
525
|
+
: 'unknown';
|
|
526
|
+
const limitations = [
|
|
527
|
+
'An observed external mention does not establish source authority, claim support, ranking impact, citation eligibility, or representative coverage.',
|
|
528
|
+
];
|
|
529
|
+
if (!independent) limitations.push('Source independence is not established from the declared control and relationship.');
|
|
530
|
+
const data = {
|
|
531
|
+
...row,
|
|
532
|
+
import_provider: document.provider,
|
|
533
|
+
import_dataset: document.dataset,
|
|
534
|
+
import_collected_at: document.collected_at,
|
|
535
|
+
coverage: document.coverage,
|
|
536
|
+
};
|
|
537
|
+
return envelope('corroboration', data, {
|
|
538
|
+
method: 'owner_import',
|
|
539
|
+
source: row.source_url,
|
|
540
|
+
raw: JSON.stringify(row),
|
|
541
|
+
confidence: independent ? 'high' : 'low',
|
|
542
|
+
limitations,
|
|
543
|
+
authority: {
|
|
544
|
+
source_authority: sourceAuthority,
|
|
545
|
+
collection_authority: document.collection_authority,
|
|
546
|
+
authenticity_status: document.authenticity_status,
|
|
547
|
+
representativeness: document.representativeness,
|
|
548
|
+
},
|
|
549
|
+
});
|
|
550
|
+
});
|
|
379
551
|
return observationRun(root, 'observe corroboration', input.file, observations, { rawInputs: { corroboration_export: input.raw } });
|
|
380
552
|
}
|
|
381
553
|
|
|
554
|
+
const PROBE_HEADER_ALLOWLIST = new Set([
|
|
555
|
+
'age', 'cache-control', 'cf-cache-status', 'cf-mitigated', 'content-type',
|
|
556
|
+
'date', 'etag', 'retry-after', 'server', 'via', 'x-cache', 'x-robots-tag',
|
|
557
|
+
]);
|
|
558
|
+
|
|
559
|
+
function safeProbeHeaders(headers = {}) {
|
|
560
|
+
return Object.fromEntries(Object.entries(headers)
|
|
561
|
+
.map(([key, value]) => [key.toLowerCase(), String(value)])
|
|
562
|
+
.filter(([key]) => PROBE_HEADER_ALLOWLIST.has(key)));
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
function classifyProbeResponse(response) {
|
|
566
|
+
const headers = safeProbeHeaders(response.headers);
|
|
567
|
+
const body = String(response.body || '').slice(0, 10000);
|
|
568
|
+
const challengeSignal = headers['cf-mitigated'] === 'challenge'
|
|
569
|
+
|| /captcha|verify you are human|attention required|access challenge/i.test(body);
|
|
570
|
+
if ([401, 403, 429].includes(response.status) || challengeSignal) return 'challenged';
|
|
571
|
+
if (response.status >= 500) return 'server_error';
|
|
572
|
+
if (response.status >= 400) return 'blocked';
|
|
573
|
+
if ((response.redirectChain || []).length) return 'redirected';
|
|
574
|
+
return response.status >= 200 && response.status < 400 ? 'accessible' : 'unexpected';
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
async function observeCrawlerProbes(root, options) {
|
|
578
|
+
if (!options.target) throw new Error('probes requires --target <public URL>');
|
|
579
|
+
const registryFile = path.join(root, '.citable', 'crawlers.yaml');
|
|
580
|
+
if (!fs.existsSync(registryFile)) throw new Error('crawlers registry is missing; run citable init first');
|
|
581
|
+
const rawRegistry = fs.readFileSync(registryFile, 'utf8');
|
|
582
|
+
const registry = readYaml(registryFile);
|
|
583
|
+
const check = validateAgainst('crawler.schema.json', registry);
|
|
584
|
+
if (!check.valid) throw new Error(`crawlers registry violates contract: ${check.errors.join('; ')}`);
|
|
585
|
+
const crawlers = registry.entries.filter((item) => item.status === 'active');
|
|
586
|
+
if (!crawlers.length) throw new Error('crawlers registry has no active identities to probe');
|
|
587
|
+
await validatePublicUrl(options.target, options.lookup ? { lookup: options.lookup } : {});
|
|
588
|
+
|
|
589
|
+
const collector = options.fetchUrl || fetchUrl;
|
|
590
|
+
const region = options.region || 'local_unspecified';
|
|
591
|
+
const observations = [], incomplete = [], artifacts = {};
|
|
592
|
+
for (const crawler of crawlers) {
|
|
593
|
+
const artifactBase = `crawler-probes/${crawler.crawler_id}`;
|
|
594
|
+
try {
|
|
595
|
+
const response = await collector(options.target, {
|
|
596
|
+
userAgent: crawler.user_agent,
|
|
597
|
+
maxRetries: 1,
|
|
598
|
+
...(options.lookup ? { lookup: options.lookup } : {}),
|
|
599
|
+
});
|
|
600
|
+
const headers = safeProbeHeaders(response.headers);
|
|
601
|
+
const edgeClassification = classifyProbeResponse(response);
|
|
602
|
+
const data = {
|
|
603
|
+
crawler_id: crawler.crawler_id,
|
|
604
|
+
vendor: crawler.vendor || null,
|
|
605
|
+
purpose: crawler.purpose,
|
|
606
|
+
declared_policy_decision: crawler.decision,
|
|
607
|
+
user_agent: crawler.user_agent,
|
|
608
|
+
target: options.target,
|
|
609
|
+
final_url: response.url || options.target,
|
|
610
|
+
status: response.status,
|
|
611
|
+
redirect_chain: response.redirectChain || [],
|
|
612
|
+
response_headers: headers,
|
|
613
|
+
response_hash: sha256(response.body || ''),
|
|
614
|
+
edge_classification: edgeClassification,
|
|
615
|
+
region,
|
|
616
|
+
identity_status: 'synthetically_observed',
|
|
617
|
+
production_access_established: false,
|
|
618
|
+
};
|
|
619
|
+
observations.push(envelope('crawler_probe', data, {
|
|
620
|
+
method: 'synthetic_fetch',
|
|
621
|
+
source: options.target,
|
|
622
|
+
raw: response.body || JSON.stringify(data),
|
|
623
|
+
confidence: 'confirmed',
|
|
624
|
+
limitations: ['A spoofed user agent in a synthetic request does not establish crawler identity or production crawler access. One local probe does not represent other regions, networks, times, or edge paths.'],
|
|
625
|
+
authority: { source_authority: 'synthetic', collection_authority: 'synthetic_probe', authenticity_status: 'unverified', representativeness: 'single_observation' },
|
|
626
|
+
}));
|
|
627
|
+
artifacts[`${artifactBase}/response.txt`] = response.body || '';
|
|
628
|
+
artifacts[`${artifactBase}/headers.json`] = headers;
|
|
629
|
+
} catch (error) {
|
|
630
|
+
incomplete.push(`${crawler.crawler_id} synthetic probe failed: ${error.message}`);
|
|
631
|
+
observations.push(envelope('crawler_probe', {
|
|
632
|
+
crawler_id: crawler.crawler_id, vendor: crawler.vendor || null, purpose: crawler.purpose,
|
|
633
|
+
declared_policy_decision: crawler.decision, user_agent: crawler.user_agent,
|
|
634
|
+
target: options.target, region, edge_classification: 'error',
|
|
635
|
+
identity_status: 'synthetically_observed', production_access_established: false,
|
|
636
|
+
}, {
|
|
637
|
+
method: 'synthetic_fetch', source: options.target, state: 'failed',
|
|
638
|
+
confidence: 'confirmed', raw: `${crawler.crawler_id}:${error.message}`,
|
|
639
|
+
limitations: [`Synthetic probe failed: ${error.message}`, 'A spoofed user agent does not establish crawler identity or production crawler access.'],
|
|
640
|
+
authority: { source_authority: 'synthetic', collection_authority: 'synthetic_probe', authenticity_status: 'unverified', representativeness: 'single_observation' },
|
|
641
|
+
}));
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
return observationRun(root, 'observe probes', options.target, observations, {
|
|
645
|
+
rawInputs: { crawler_registry: rawRegistry }, incomplete, artifacts,
|
|
646
|
+
warnings: ['Probe outcomes are synthetic retrieval evidence only and must not be interpreted as verified crawler identity or production access.'],
|
|
647
|
+
});
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
function observeRegionalNetwork(root, options) {
|
|
651
|
+
const input = readInput(options.input);
|
|
652
|
+
const document = input.value;
|
|
653
|
+
const check = validateAgainst('regional-network-import.schema.json', document);
|
|
654
|
+
if (!check.valid) throw new Error(`regional network import violates contract: ${check.errors.join('; ')}`);
|
|
655
|
+
const limitations = ['Imported regional observations do not establish managed runner coverage, global representativeness, crawler identity, or future availability.'];
|
|
656
|
+
const observations = document.probes.map((probe) => envelope('regional_network', {
|
|
657
|
+
...probe,
|
|
658
|
+
provider: document.provider,
|
|
659
|
+
dataset_collected_at: document.collected_at,
|
|
660
|
+
}, {
|
|
661
|
+
method: 'owner_import',
|
|
662
|
+
source: input.file,
|
|
663
|
+
raw: JSON.stringify(probe),
|
|
664
|
+
confidence: 'confirmed',
|
|
665
|
+
limitations,
|
|
666
|
+
authority: {
|
|
667
|
+
source_authority: 'unknown',
|
|
668
|
+
collection_authority: document.collection_authority,
|
|
669
|
+
authenticity_status: document.authenticity_status,
|
|
670
|
+
representativeness: document.representativeness,
|
|
671
|
+
},
|
|
672
|
+
}));
|
|
673
|
+
return observationRun(root, 'observe network', input.file, observations, {
|
|
674
|
+
rawInputs: { regional_network_export: input.raw },
|
|
675
|
+
warnings: ['This release validates regional probe exports but does not operate a managed multi-region runner.'],
|
|
676
|
+
});
|
|
677
|
+
}
|
|
678
|
+
|
|
382
679
|
async function observeRepresentation(root, options) {
|
|
383
680
|
const input = readInput(options.input);
|
|
384
681
|
const manifest = input.value;
|
|
@@ -461,8 +758,11 @@ export async function observe(root, mode, options = {}) {
|
|
|
461
758
|
case 'consensus': return observeConsensus(root, options);
|
|
462
759
|
case 'performance': return observePerformance(root, options);
|
|
463
760
|
case 'corroboration': return observeCorroboration(root, options);
|
|
761
|
+
case 'probes': return observeCrawlerProbes(root, options);
|
|
762
|
+
case 'network': return observeRegionalNetwork(root, options);
|
|
464
763
|
case 'media': return observeMedia(root, options);
|
|
465
764
|
case 'representation': return observeRepresentation(root, options);
|
|
466
|
-
|
|
765
|
+
case 'stance': return observeStance(root, options);
|
|
766
|
+
default: throw new Error('observe mode must be render, index, citations, logs, bing, passages, consensus, performance, corroboration, probes, network, media, representation, or stance');
|
|
467
767
|
}
|
|
468
768
|
}
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import { buildContext } from './context.js';
|
|
2
|
+
import { runtimeCapabilities } from '../installer/index.js';
|
|
3
|
+
|
|
4
|
+
const PROFILE_RULES = [
|
|
5
|
+
{
|
|
6
|
+
id: 'saas',
|
|
7
|
+
signals: [
|
|
8
|
+
['pricing URL discovered', ({ paths }) => paths.some((value) => /\/pricing(?:\/|$)/.test(value))],
|
|
9
|
+
['product, feature, integration, or documentation URL discovered', ({ paths }) => paths.some((value) => /\/(?:products?|features?|integrations?|docs)(?:\/|$)/.test(value))],
|
|
10
|
+
['software or platform language observed', ({ text }) => /\b(?:software|saas|platform)\b/i.test(text)],
|
|
11
|
+
['trial or signup language observed', ({ text }) => /\b(?:free trial|sign up|get started)\b/i.test(text)],
|
|
12
|
+
],
|
|
13
|
+
emphasis: ['pricing-and-capability-claim-support', 'product-entity-consistency'],
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
id: 'ecommerce',
|
|
17
|
+
signals: [
|
|
18
|
+
['product or collection URL discovered', ({ paths }) => paths.some((value) => /\/(?:products?|collections?|cart)(?:\/|$)/.test(value))],
|
|
19
|
+
['commerce action language observed', ({ text }) => /\b(?:add to cart|buy now|checkout)\b/i.test(text)],
|
|
20
|
+
['Product or Offer structured data observed', ({ schemaTypes }) => schemaTypes.has('Product') || schemaTypes.has('Offer')],
|
|
21
|
+
],
|
|
22
|
+
emphasis: ['product-offer-visible-content-support', 'merchant-claim-governance'],
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
id: 'local',
|
|
26
|
+
signals: [
|
|
27
|
+
['LocalBusiness structured data observed', ({ schemaTypes }) => schemaTypes.has('LocalBusiness')],
|
|
28
|
+
['telephone link observed', ({ hrefs }) => hrefs.some((value) => value.startsWith('tel:'))],
|
|
29
|
+
['local-service language observed', ({ text }) => /\b(?:service area|serving [a-z]|visit us|directions)\b/i.test(text)],
|
|
30
|
+
['map link or embed observed', ({ rawHtml }) => /(?:google\.[^"']*\/maps|maps\.google)/i.test(rawHtml)],
|
|
31
|
+
],
|
|
32
|
+
emphasis: ['local-entity-and-nap-consistency', 'review-and-rating-evidence'],
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
id: 'publisher',
|
|
36
|
+
signals: [
|
|
37
|
+
['article, blog, or topic URL discovered', ({ paths }) => paths.some((value) => /\/(?:blog|articles?|topics?)(?:\/|$)/.test(value))],
|
|
38
|
+
['Article-family structured data observed', ({ schemaTypes }) => ['Article', 'BlogPosting', 'NewsArticle'].some((value) => schemaTypes.has(value))],
|
|
39
|
+
['author or publication-date language observed', ({ text }) => /\b(?:written by|published|updated on)\b/i.test(text)],
|
|
40
|
+
],
|
|
41
|
+
emphasis: ['authorship-and-source-identity', 'freshness-consensus'],
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
id: 'multilingual',
|
|
45
|
+
signals: [
|
|
46
|
+
['multiple document languages observed', ({ languages }) => languages.size > 1],
|
|
47
|
+
['hreflang markup observed', ({ rawHtml }) => /hreflang\s*=/i.test(rawHtml)],
|
|
48
|
+
],
|
|
49
|
+
emphasis: ['hreflang-reciprocity-and-language-parity'],
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
id: 'api-docs',
|
|
53
|
+
signals: [
|
|
54
|
+
['API or documentation URL discovered', ({ paths }) => paths.some((value) => /\/(?:api|docs?|developers?)(?:\/|$)/.test(value))],
|
|
55
|
+
['API or SDK language observed', ({ text }) => /\b(?:api|sdk|webhook|endpoint)\b/i.test(text)],
|
|
56
|
+
['machine interface metadata observed', ({ paths }) => paths.some((value) => /\/(?:agent\.json|auth\.md|\.well-known\/)/.test(value))],
|
|
57
|
+
],
|
|
58
|
+
emphasis: ['machine-interface-discoverability', 'capability-and-authentication-claim-support'],
|
|
59
|
+
},
|
|
60
|
+
];
|
|
61
|
+
|
|
62
|
+
const COLLECTORS = [
|
|
63
|
+
['render', 'browser_render', 'citable observe render --target TARGET'],
|
|
64
|
+
['lighthouse', 'lighthouse_lab', 'citable observe performance --target TARGET --lighthouse --repeat 3'],
|
|
65
|
+
['crux', 'crux_field', 'citable observe performance --target TARGET'],
|
|
66
|
+
['gsc-index', 'gsc_index', 'citable observe index --target TARGET --site-url PROPERTY'],
|
|
67
|
+
['ga4-metrics', 'ga4_metrics', 'citable connect sync --connection-id CONNECTION --start-date DATE --end-date DATE'],
|
|
68
|
+
];
|
|
69
|
+
|
|
70
|
+
function siteSignals(site) {
|
|
71
|
+
const paths = [];
|
|
72
|
+
const hrefs = [];
|
|
73
|
+
const schemaTypes = new Set();
|
|
74
|
+
const languages = new Set();
|
|
75
|
+
let text = '';
|
|
76
|
+
let rawHtml = '';
|
|
77
|
+
for (const page of site.pages) {
|
|
78
|
+
paths.push(new URL(page.url).pathname);
|
|
79
|
+
hrefs.push(...page.links.map((link) => link.href));
|
|
80
|
+
text += ` ${page.text}`;
|
|
81
|
+
rawHtml += ` ${page.rawHtml}`;
|
|
82
|
+
if (page.lang) languages.add(page.lang.toLowerCase());
|
|
83
|
+
for (const item of page.jsonLd) {
|
|
84
|
+
for (const block of item.blocks) {
|
|
85
|
+
const types = Array.isArray(block?.['@type']) ? block['@type'] : [block?.['@type']];
|
|
86
|
+
for (const type of types.filter(Boolean)) schemaTypes.add(type);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
return { paths, hrefs, schemaTypes, languages, text, rawHtml };
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function classifyProfiles(site) {
|
|
94
|
+
const signals = siteSignals(site);
|
|
95
|
+
return PROFILE_RULES.map((rule) => {
|
|
96
|
+
const evidence = rule.signals.filter(([, matches]) => matches(signals)).map(([label]) => label);
|
|
97
|
+
return {
|
|
98
|
+
id: rule.id,
|
|
99
|
+
confidence: evidence.length >= 3 ? 'high' : evidence.length === 2 ? 'medium' : 'low',
|
|
100
|
+
evidence,
|
|
101
|
+
emphasis: rule.emphasis,
|
|
102
|
+
};
|
|
103
|
+
}).filter((profile) => profile.evidence.length >= 2);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export async function planAudit(root, { target, baseUrl, refDate, capabilities } = {}) {
|
|
107
|
+
if (!target) throw new Error('plan-audit requires --target <dir|url>');
|
|
108
|
+
const ctx = await buildContext(root, { target, baseUrl, refDate });
|
|
109
|
+
if (!ctx.site) throw new Error('plan-audit could not build a site model');
|
|
110
|
+
const profiles = classifyProfiles(ctx.site);
|
|
111
|
+
const runtime = capabilities ?? await runtimeCapabilities();
|
|
112
|
+
const capabilityById = new Map(runtime.map((item) => [item.id, item]));
|
|
113
|
+
const collectors = COLLECTORS.map(([id, capabilityId, command]) => {
|
|
114
|
+
const capability = capabilityById.get(capabilityId);
|
|
115
|
+
const status = capability?.state === 'ready' ? 'available' : 'blocked';
|
|
116
|
+
return {
|
|
117
|
+
id,
|
|
118
|
+
status,
|
|
119
|
+
capability: capabilityId,
|
|
120
|
+
command: command.replace('TARGET', JSON.stringify(target)),
|
|
121
|
+
required_input: status === 'available' ? [] : capability?.requirements ?? [],
|
|
122
|
+
limitations: capability?.limitations ?? ['capability status is unavailable'],
|
|
123
|
+
};
|
|
124
|
+
});
|
|
125
|
+
return {
|
|
126
|
+
target: {
|
|
127
|
+
kind: ctx.site.mode,
|
|
128
|
+
location: ctx.site.location,
|
|
129
|
+
pages_inspected: ctx.site.pages.length,
|
|
130
|
+
},
|
|
131
|
+
classification: {
|
|
132
|
+
statement_type: 'probabilistic_inference',
|
|
133
|
+
profiles,
|
|
134
|
+
unresolved: profiles.length ? [] : ['No profile met the two-signal evidence threshold.'],
|
|
135
|
+
},
|
|
136
|
+
audit: {
|
|
137
|
+
scope: 'all',
|
|
138
|
+
command: `citable audit --target ${JSON.stringify(target)}${baseUrl ? ` --base-url ${JSON.stringify(baseUrl)}` : ''}`,
|
|
139
|
+
rationale: 'The full audit preserves separate SEO, AEO, GEO, governance, and observation boundaries; profile inference changes emphasis, not detector truth.',
|
|
140
|
+
},
|
|
141
|
+
semantic_emphasis: [...new Set(profiles.flatMap((profile) => profile.emphasis))],
|
|
142
|
+
collectors,
|
|
143
|
+
warnings: ctx.warnings,
|
|
144
|
+
boundaries: [
|
|
145
|
+
'No audit or observation package was created.',
|
|
146
|
+
'Profile labels are probabilistic inferences from captured page signals, not verified business facts.',
|
|
147
|
+
'Available collectors establish prerequisite presence only; execution and evidence remain unproven until a collector completes.',
|
|
148
|
+
'Blocked collectors do not make the proposed audit a success or failure; they identify unavailable follow-up evidence.',
|
|
149
|
+
],
|
|
150
|
+
};
|
|
151
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { buildTimeSeries, loadRunHistory, renderDashboardHtml, renderDashboardMarkdown } from '../reporting/dashboard.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* `citable report dashboard` — fold the summary already recorded by each audit
|
|
7
|
+
* run into a cross-run Markdown table and a self-contained HTML page. Derived
|
|
8
|
+
* output only: it never reads, edits, or re-derives an immutable run package.
|
|
9
|
+
*/
|
|
10
|
+
export function reportDashboard(root, { since, last } = {}) {
|
|
11
|
+
if (last != null && (!Number.isInteger(last) || last < 1)) throw new Error('--last must be a positive integer');
|
|
12
|
+
const { included, skipped } = loadRunHistory(root, { since, last });
|
|
13
|
+
const series = buildTimeSeries(included);
|
|
14
|
+
const dir = path.join(root, '.citable', 'reports');
|
|
15
|
+
const pathMd = path.join(dir, 'dashboard.md');
|
|
16
|
+
const pathHtml = path.join(dir, 'dashboard.html');
|
|
17
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
18
|
+
fs.writeFileSync(pathMd, renderDashboardMarkdown(series, skipped));
|
|
19
|
+
fs.writeFileSync(pathHtml, renderDashboardHtml(series, skipped));
|
|
20
|
+
return { dir, included: included.length, skipped: skipped.length, path_md: pathMd, path_html: pathHtml };
|
|
21
|
+
}
|