@nebulacomponents/citable 1.13.1 → 1.14.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 +246 -0
- package/README.md +43 -5
- package/dist/universal/.agents/skills/citable/SKILL.md +13 -4
- 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/action-findings.md +9 -1
- package/dist/universal/.agents/skills/citable/commands/observe-and-act.md +50 -3
- package/dist/universal/.agents/skills/citable/commands/plan-audit.md +35 -0
- package/dist/universal/.agents/skills/citable/manifest.json +26 -14
- 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/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 +13 -4
- 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/action-findings.md +9 -1
- package/dist/universal/.claude/skills/citable/commands/observe-and-act.md +50 -3
- package/dist/universal/.claude/skills/citable/commands/plan-audit.md +35 -0
- package/dist/universal/.claude/skills/citable/manifest.json +31 -14
- 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/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 +13 -4
- 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/action-findings.md +9 -1
- package/dist/universal/.cursor/skills/citable/commands/observe-and-act.md +50 -3
- package/dist/universal/.cursor/skills/citable/commands/plan-audit.md +35 -0
- package/dist/universal/.cursor/skills/citable/manifest.json +26 -14
- 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/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 +13 -4
- 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/action-findings.md +9 -1
- package/dist/universal/.gemini/skills/citable/commands/observe-and-act.md +50 -3
- package/dist/universal/.gemini/skills/citable/commands/plan-audit.md +35 -0
- package/dist/universal/.gemini/skills/citable/manifest.json +26 -14
- 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/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 +13 -4
- 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/action-findings.md +9 -1
- package/dist/universal/.github/skills/citable/commands/observe-and-act.md +50 -3
- package/dist/universal/.github/skills/citable/commands/plan-audit.md +35 -0
- package/dist/universal/.github/skills/citable/manifest.json +26 -14
- 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/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 +13 -4
- 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/action-findings.md +9 -1
- package/dist/universal/.kiro/skills/citable/commands/observe-and-act.md +50 -3
- package/dist/universal/.kiro/skills/citable/commands/plan-audit.md +35 -0
- package/dist/universal/.kiro/skills/citable/manifest.json +26 -14
- 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/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 +13 -4
- 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/action-findings.md +9 -1
- package/dist/universal/.opencode/skills/citable/commands/observe-and-act.md +50 -3
- package/dist/universal/.opencode/skills/citable/commands/plan-audit.md +35 -0
- package/dist/universal/.opencode/skills/citable/manifest.json +26 -14
- 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/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 +13 -4
- 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/action-findings.md +9 -1
- package/dist/universal/.pi/agent/skills/citable/commands/observe-and-act.md +50 -3
- package/dist/universal/.pi/agent/skills/citable/commands/plan-audit.md +35 -0
- package/dist/universal/.pi/agent/skills/citable/manifest.json +26 -14
- 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/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 +13 -4
- 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/action-findings.md +9 -1
- package/dist/universal/.qoder/skills/citable/commands/observe-and-act.md +50 -3
- package/dist/universal/.qoder/skills/citable/commands/plan-audit.md +35 -0
- package/dist/universal/.qoder/skills/citable/manifest.json +26 -14
- 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/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 +13 -4
- 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/action-findings.md +9 -1
- package/dist/universal/.rovodev/skills/citable/commands/observe-and-act.md +50 -3
- package/dist/universal/.rovodev/skills/citable/commands/plan-audit.md +35 -0
- package/dist/universal/.rovodev/skills/citable/manifest.json +26 -14
- 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/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 +13 -4
- 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/action-findings.md +9 -1
- package/dist/universal/.trae/skills/citable/commands/observe-and-act.md +50 -3
- package/dist/universal/.trae/skills/citable/commands/plan-audit.md +35 -0
- package/dist/universal/.trae/skills/citable/manifest.json +26 -14
- 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/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 +13 -4
- 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/action-findings.md +9 -1
- package/dist/universal/.trae-cn/skills/citable/commands/observe-and-act.md +50 -3
- package/dist/universal/.trae-cn/skills/citable/commands/plan-audit.md +35 -0
- package/dist/universal/.trae-cn/skills/citable/manifest.json +26 -14
- 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/source-identity-chain.schema.json +83 -0
- package/dist/universal/.trae-cn/skills/citable/scripts/README.md +3 -1
- package/dist/universal/manifest.json +270 -171
- 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/source-identity-chain.schema.json +83 -0
- package/scripts/build-dist.js +36 -3
- package/skill/SKILL.md +13 -4
- package/skill/agents/citable-auditor.md +64 -0
- package/skill/agents/citable-semantic-reviewer.md +66 -0
- package/skill/commands/action-findings.md +9 -1
- package/skill/commands/observe-and-act.md +50 -3
- package/skill/commands/plan-audit.md +35 -0
- package/src/cli/index.js +38 -5
- package/src/commands/actionPlan.js +12 -1
- package/src/commands/audit.js +4 -0
- 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/observe.js +313 -15
- package/src/commands/planAudit.js +151 -0
- package/src/commands/schemaCmd.js +5 -1
- package/src/commands/substantiate.js +22 -10
- package/src/crawler/fetch.js +34 -6
- package/src/extractor/page.js +17 -0
- package/src/installer/index.js +282 -28
- package/src/installer/providers.js +11 -0
- package/src/observations/browserJourney.js +15 -7
- package/src/observations/claimDiff.js +83 -0
- package/src/observations/common.js +15 -0
- package/src/observations/entityGraph.js +89 -0
- package/src/observations/renderParity.js +49 -0
- package/src/observations/sourceIdentity.js +62 -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/shared/checklist.js +19 -0
package/src/installer/index.js
CHANGED
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
PROVIDERS,
|
|
9
9
|
PROVIDER_IDS,
|
|
10
10
|
parseProviderList,
|
|
11
|
+
providerAgentProfilesDestination,
|
|
11
12
|
providerBundleSkillPath,
|
|
12
13
|
providerDestination,
|
|
13
14
|
providerSkillsDir,
|
|
@@ -421,7 +422,7 @@ function toPosix(value) {
|
|
|
421
422
|
|
|
422
423
|
function makeInstalledManifest(target, packageInfo, tree) {
|
|
423
424
|
return {
|
|
424
|
-
name: 'citable',
|
|
425
|
+
name: target.manifestName || 'citable',
|
|
425
426
|
version: packageInfo.version,
|
|
426
427
|
managedBy: 'citable-cli',
|
|
427
428
|
provider: target.providerId,
|
|
@@ -471,7 +472,7 @@ export function inspectInstallation(target, options = {}) {
|
|
|
471
472
|
} catch (err) {
|
|
472
473
|
return { state: 'corrupt', installed: null, path: destination, problems: [`manifest parse failed: ${err.message}`], symlink, localModifications: 'unknown' };
|
|
473
474
|
}
|
|
474
|
-
if (manifest.managedBy !== 'citable-cli' || manifest.name !== 'citable') {
|
|
475
|
+
if (manifest.managedBy !== 'citable-cli' || manifest.name !== (target.manifestName || 'citable')) {
|
|
475
476
|
return { state: 'unmanaged', installed: manifest.version ?? null, path: destination, problems: ['manifest is not managed by citable-cli'], manifest, symlink, localModifications: 'unknown' };
|
|
476
477
|
}
|
|
477
478
|
|
|
@@ -543,6 +544,13 @@ export function createInstallPlan(command, args, options = {}) {
|
|
|
543
544
|
}
|
|
544
545
|
const status = inspectInstallation(target, { availableTree: bundle.tree, availableVersion: packageInfo.version });
|
|
545
546
|
const action = plannedAction(command, status, args);
|
|
547
|
+
let profiles = null;
|
|
548
|
+
try {
|
|
549
|
+
profiles = prepareAgentProfiles(target, bundle, packageInfo, args, command, options);
|
|
550
|
+
} catch (err) {
|
|
551
|
+
results.push(targetResult(target, 'failed', err.message, { exitCode: err.exitCode ?? EXIT_CODES.integrityFailure }));
|
|
552
|
+
continue;
|
|
553
|
+
}
|
|
546
554
|
results.push({
|
|
547
555
|
provider: target.providerId,
|
|
548
556
|
providerName: target.provider.displayName,
|
|
@@ -558,6 +566,16 @@ export function createInstallPlan(command, args, options = {}) {
|
|
|
558
566
|
filesToRemove: [],
|
|
559
567
|
collisions: collisionProblems(status, args),
|
|
560
568
|
localModifications: status.localModifications,
|
|
569
|
+
agentProfiles: profiles ? {
|
|
570
|
+
path: profiles.target.destination,
|
|
571
|
+
state: profiles.before.state,
|
|
572
|
+
action: profiles.action,
|
|
573
|
+
filesToCreate: ['install', 'replace', 'update', 'repair'].includes(profiles.action)
|
|
574
|
+
? Object.keys(profiles.tree.files)
|
|
575
|
+
: [],
|
|
576
|
+
collisions: collisionProblems(profiles.before, args),
|
|
577
|
+
localModifications: profiles.before.localModifications,
|
|
578
|
+
} : null,
|
|
561
579
|
validation: ['validate package bundle', 'validate provider target', 'validate staged skill', 'validate final installation'],
|
|
562
580
|
});
|
|
563
581
|
}
|
|
@@ -674,41 +692,65 @@ function inspectInstallationWithBundle(target, options = {}) {
|
|
|
674
692
|
function installOrUpdateTarget(target, args, options = {}, command = 'install') {
|
|
675
693
|
let stage = null;
|
|
676
694
|
let backup = null;
|
|
695
|
+
let installedSkill = false;
|
|
677
696
|
try {
|
|
678
697
|
const packageInfo = loadPackageInfo(options.packageRoot || packageRoot());
|
|
679
698
|
const bundle = validateProviderBundle(target.providerId, options);
|
|
680
699
|
const before = inspectInstallation(target, { availableTree: bundle.tree, availableVersion: packageInfo.version });
|
|
681
700
|
const action = plannedAction(command, before, args);
|
|
682
|
-
|
|
701
|
+
const profiles = prepareAgentProfiles(target, bundle, packageInfo, args, command, options);
|
|
702
|
+
if (action === 'already current' && (!profiles || profiles.action === 'already current')) {
|
|
703
|
+
return targetResult(target, 'already current', 'already current', { before, agentProfiles: profiles?.before ?? null });
|
|
704
|
+
}
|
|
683
705
|
if (action === 'not installed') return targetResult(target, 'skipped', 'not installed', { before });
|
|
684
706
|
if (action === 'refuse') {
|
|
685
707
|
const code = before.state === 'unmanaged' ? EXIT_CODES.installationCollision : EXIT_CODES.integrityFailure;
|
|
686
708
|
return targetResult(target, 'failed', `${before.state}: ${before.problems.join('; ') || 'refusing to overwrite without --force'}`, { before, exitCode: code });
|
|
687
709
|
}
|
|
710
|
+
if (profiles?.action === 'refuse') {
|
|
711
|
+
const code = profiles.before.state === 'unmanaged' ? EXIT_CODES.installationCollision : EXIT_CODES.integrityFailure;
|
|
712
|
+
return targetResult(target, 'failed', `agent profile ${profiles.before.state}: ${profiles.before.problems.join('; ') || 'refusing to overwrite without --force'}`, {
|
|
713
|
+
before,
|
|
714
|
+
agentProfiles: profiles.before,
|
|
715
|
+
exitCode: code,
|
|
716
|
+
});
|
|
717
|
+
}
|
|
688
718
|
|
|
689
719
|
const installPath = target.realDestination || target.destination;
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
720
|
+
let after = before;
|
|
721
|
+
if (action !== 'already current') {
|
|
722
|
+
const parent = path.dirname(installPath);
|
|
723
|
+
fs.mkdirSync(parent, { recursive: true });
|
|
724
|
+
ensureTargetInsideScope(parent, target.scopeRoot);
|
|
725
|
+
stage = path.join(parent, `.citable-install-${process.pid}-${Date.now()}-${target.providerId}`);
|
|
726
|
+
backup = path.join(parent, `.citable-backup-${Date.now()}-${target.providerId}`);
|
|
727
|
+
copyDirSafe(bundle.source, stage);
|
|
728
|
+
const stagedTree = hashTree(stage, { exclude: ['manifest.json'] });
|
|
729
|
+
writeJsonFile(path.join(stage, 'manifest.json'), makeInstalledManifest(target, packageInfo, stagedTree));
|
|
730
|
+
validateStagedSkill(stage, target.providerId);
|
|
731
|
+
|
|
732
|
+
if (exists(installPath)) fs.renameSync(installPath, backup);
|
|
733
|
+
fs.renameSync(stage, installPath);
|
|
734
|
+
stage = null;
|
|
735
|
+
installedSkill = true;
|
|
736
|
+
|
|
737
|
+
after = inspectInstallation(target, { availableTree: stagedTree, availableVersion: packageInfo.version });
|
|
738
|
+
if (after.state !== 'current') {
|
|
739
|
+
throw new CitableInstallerError(`final validation failed: ${after.state}`, EXIT_CODES.integrityFailure, { after });
|
|
740
|
+
}
|
|
707
741
|
}
|
|
708
|
-
|
|
742
|
+
const profileResult = profiles ? installAgentProfiles(profiles, packageInfo) : null;
|
|
743
|
+
const resultStatus = action === 'update' || (action === 'already current' && profileResult?.changed) ? 'updated' : 'installed';
|
|
744
|
+
return targetResult(target, resultStatus, `${action === 'already current' ? 'agent profile install' : action} complete`, {
|
|
745
|
+
before,
|
|
746
|
+
after,
|
|
747
|
+
agentProfiles: profileResult,
|
|
748
|
+
backup: exists(backup) ? backup : null,
|
|
749
|
+
});
|
|
709
750
|
} catch (err) {
|
|
710
751
|
if (stage) safeRm(stage);
|
|
711
752
|
const installPath = target.realDestination || target.destination;
|
|
753
|
+
if (installedSkill && exists(installPath)) safeRm(installPath);
|
|
712
754
|
if (backup && exists(backup) && !exists(installPath)) {
|
|
713
755
|
try {
|
|
714
756
|
fs.renameSync(backup, installPath);
|
|
@@ -720,6 +762,80 @@ function installOrUpdateTarget(target, args, options = {}, command = 'install')
|
|
|
720
762
|
}
|
|
721
763
|
}
|
|
722
764
|
|
|
765
|
+
function prepareAgentProfiles(target, bundle, packageInfo, args, command, options) {
|
|
766
|
+
const metadata = bundle.manifest.agentProfiles;
|
|
767
|
+
if (!metadata || metadata.status !== 'available') return null;
|
|
768
|
+
const destination = providerAgentProfilesDestination(target.providerId, target.scope, rootsFor(options));
|
|
769
|
+
if (!destination) throw new CitableInstallerError(`agent profiles are not supported for ${target.provider.displayName}`, EXIT_CODES.unsupportedEnvironment);
|
|
770
|
+
const source = path.resolve(bundleRoot(options.packageRoot || packageRoot()), metadata.path);
|
|
771
|
+
const sourceManifest = readJson(path.join(source, 'manifest.json'), `${target.providerId} agent profile manifest`);
|
|
772
|
+
if (
|
|
773
|
+
sourceManifest.name !== 'citable-agent-profiles'
|
|
774
|
+
|| sourceManifest.provider !== target.providerId
|
|
775
|
+
|| sourceManifest.managedBy !== 'citable-cli'
|
|
776
|
+
) {
|
|
777
|
+
throw new CitableInstallerError(`agent profile manifest for ${target.provider.displayName} is invalid`, EXIT_CODES.integrityFailure);
|
|
778
|
+
}
|
|
779
|
+
const tree = hashTree(source, { exclude: ['manifest.json'] });
|
|
780
|
+
if (tree.treeHash !== metadata.treeHash || tree.treeHash !== sourceManifest.treeHash) {
|
|
781
|
+
throw new CitableInstallerError(`agent profile bundle hash mismatch for ${target.provider.displayName}`, EXIT_CODES.integrityFailure);
|
|
782
|
+
}
|
|
783
|
+
const profileTarget = {
|
|
784
|
+
...target,
|
|
785
|
+
destination,
|
|
786
|
+
realDestination: null,
|
|
787
|
+
manifestName: 'citable-agent-profiles',
|
|
788
|
+
};
|
|
789
|
+
const before = inspectInstallation(profileTarget, { availableTree: tree, availableVersion: packageInfo.version });
|
|
790
|
+
return {
|
|
791
|
+
target: profileTarget,
|
|
792
|
+
source,
|
|
793
|
+
tree,
|
|
794
|
+
before,
|
|
795
|
+
action: plannedAction(command, before, args),
|
|
796
|
+
};
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
function installAgentProfiles(profiles, packageInfo) {
|
|
800
|
+
if (profiles.action === 'already current') return { changed: false, before: profiles.before, after: profiles.before };
|
|
801
|
+
const destination = profiles.target.destination;
|
|
802
|
+
const parent = path.dirname(destination);
|
|
803
|
+
let stage = null;
|
|
804
|
+
let backup = null;
|
|
805
|
+
try {
|
|
806
|
+
fs.mkdirSync(parent, { recursive: true });
|
|
807
|
+
ensureTargetInsideScope(parent, profiles.target.scopeRoot);
|
|
808
|
+
stage = path.join(parent, `.citable-agent-install-${process.pid}-${Date.now()}`);
|
|
809
|
+
backup = path.join(parent, `.citable-agent-backup-${Date.now()}`);
|
|
810
|
+
copyDirSafe(profiles.source, stage);
|
|
811
|
+
writeJsonFile(path.join(stage, 'manifest.json'), makeInstalledManifest(profiles.target, packageInfo, profiles.tree));
|
|
812
|
+
validateStagedProfiles(stage, profiles.target.providerId, profiles.tree);
|
|
813
|
+
if (exists(destination)) fs.renameSync(destination, backup);
|
|
814
|
+
fs.renameSync(stage, destination);
|
|
815
|
+
stage = null;
|
|
816
|
+
const after = inspectInstallation(profiles.target, { availableTree: profiles.tree, availableVersion: packageInfo.version });
|
|
817
|
+
if (after.state !== 'current') throw new CitableInstallerError(`final agent profile validation failed: ${after.state}`, EXIT_CODES.integrityFailure);
|
|
818
|
+
return { changed: true, before: profiles.before, after, backup: exists(backup) ? backup : null };
|
|
819
|
+
} catch (err) {
|
|
820
|
+
if (stage) safeRm(stage);
|
|
821
|
+
if (exists(destination)) safeRm(destination);
|
|
822
|
+
if (backup && exists(backup)) fs.renameSync(backup, destination);
|
|
823
|
+
throw err;
|
|
824
|
+
}
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
function validateStagedProfiles(dir, providerId, tree) {
|
|
828
|
+
for (const rel of Object.keys(tree.files)) {
|
|
829
|
+
if (!exists(path.join(dir, rel))) {
|
|
830
|
+
throw new CitableInstallerError(`staged ${providerId} agent profiles are missing ${rel}`, EXIT_CODES.integrityFailure);
|
|
831
|
+
}
|
|
832
|
+
}
|
|
833
|
+
const manifest = readJson(path.join(dir, 'manifest.json'), 'staged agent profile manifest');
|
|
834
|
+
if (manifest.managedBy !== 'citable-cli' || manifest.provider !== providerId || manifest.name !== 'citable-agent-profiles') {
|
|
835
|
+
throw new CitableInstallerError(`staged ${providerId} agent profile manifest is invalid`, EXIT_CODES.integrityFailure);
|
|
836
|
+
}
|
|
837
|
+
}
|
|
838
|
+
|
|
723
839
|
function ensureTargetInsideScope(targetPath, scopeRoot) {
|
|
724
840
|
// Use realpathSync when possible to resolve symlinks (e.g. /tmp → /private/tmp on macOS)
|
|
725
841
|
// so that cross-symlink paths compare correctly.
|
|
@@ -777,6 +893,7 @@ function uninstallTarget(target, args) {
|
|
|
777
893
|
if (status.manifest?.managedBy !== 'citable-cli') {
|
|
778
894
|
return targetResult(target, 'failed', `${status.state}: refusing to remove unmanaged content`, { before: status, exitCode: EXIT_CODES.installationCollision });
|
|
779
895
|
}
|
|
896
|
+
const profileResult = uninstallAgentProfiles(target);
|
|
780
897
|
const destination = target.realDestination || target.destination;
|
|
781
898
|
const relFiles = Object.keys(status.manifest.files || {}).sort((a, b) => b.localeCompare(a));
|
|
782
899
|
for (const rel of relFiles) {
|
|
@@ -790,12 +907,39 @@ function uninstallTarget(target, args) {
|
|
|
790
907
|
} catch {
|
|
791
908
|
// Directory contains user files; preserve it.
|
|
792
909
|
}
|
|
793
|
-
return targetResult(target, 'removed', 'removed managed files', {
|
|
910
|
+
return targetResult(target, 'removed', 'removed managed files', {
|
|
911
|
+
before: status,
|
|
912
|
+
agentProfiles: profileResult,
|
|
913
|
+
removedFiles: [...relFiles, 'manifest.json'],
|
|
914
|
+
});
|
|
794
915
|
} catch (err) {
|
|
795
916
|
return targetResult(target, 'failed', err.message, { exitCode: err.exitCode ?? EXIT_CODES.generalFailure });
|
|
796
917
|
}
|
|
797
918
|
}
|
|
798
919
|
|
|
920
|
+
function uninstallAgentProfiles(target) {
|
|
921
|
+
const destination = providerAgentProfilesDestination(target.providerId, target.scope, {
|
|
922
|
+
projectRoot: target.scope === 'project' ? target.scopeRoot : '',
|
|
923
|
+
home: target.scope === 'global' ? target.scopeRoot : '',
|
|
924
|
+
});
|
|
925
|
+
if (!destination) return null;
|
|
926
|
+
const profileTarget = { ...target, destination, realDestination: null, manifestName: 'citable-agent-profiles' };
|
|
927
|
+
const status = inspectInstallation(profileTarget);
|
|
928
|
+
if (status.state === 'not installed') return { status: 'not installed' };
|
|
929
|
+
if (status.manifest?.managedBy !== 'citable-cli' || status.manifest?.name !== 'citable-agent-profiles') {
|
|
930
|
+
return { status: 'preserved', state: status.state, problems: status.problems };
|
|
931
|
+
}
|
|
932
|
+
const relFiles = Object.keys(status.manifest.files || {}).sort((a, b) => b.localeCompare(a));
|
|
933
|
+
for (const rel of relFiles) {
|
|
934
|
+
assertArchiveEntrySafe(rel);
|
|
935
|
+
safeUnlink(path.join(destination, rel));
|
|
936
|
+
}
|
|
937
|
+
safeUnlink(path.join(destination, 'manifest.json'));
|
|
938
|
+
pruneEmptyDirs(destination, destination);
|
|
939
|
+
if (exists(destination) && fs.readdirSync(destination).length === 0) fs.rmdirSync(destination);
|
|
940
|
+
return { status: 'removed', removedFiles: [...relFiles, 'manifest.json'] };
|
|
941
|
+
}
|
|
942
|
+
|
|
799
943
|
function safeUnlink(filePath) {
|
|
800
944
|
try {
|
|
801
945
|
fs.unlinkSync(filePath);
|
|
@@ -841,13 +985,31 @@ async function inspectCommand(command, args, options = {}) {
|
|
|
841
985
|
const packageInfo = loadPackageInfo(context.roots.packageRoot);
|
|
842
986
|
const rows = [];
|
|
843
987
|
for (const target of context.targets.filter((target) => !target.duplicateOf)) {
|
|
988
|
+
let bundle = null;
|
|
844
989
|
let bundleTree = null;
|
|
845
990
|
try {
|
|
846
|
-
|
|
991
|
+
bundle = validateProviderBundle(target.providerId, { ...options, packageRoot: context.roots.packageRoot });
|
|
992
|
+
bundleTree = bundle.tree;
|
|
847
993
|
} catch {
|
|
848
994
|
// Check/list should still report installed state if the package bundle is broken.
|
|
849
995
|
}
|
|
850
996
|
const status = inspectInstallation(target, { availableTree: bundleTree, availableVersion: packageInfo.version });
|
|
997
|
+
let agentProfiles = null;
|
|
998
|
+
if (bundle) {
|
|
999
|
+
try {
|
|
1000
|
+
agentProfiles = prepareAgentProfiles(target, bundle, packageInfo, args, 'install', options)?.before ?? null;
|
|
1001
|
+
} catch (err) {
|
|
1002
|
+
agentProfiles = {
|
|
1003
|
+
state: 'corrupt',
|
|
1004
|
+
installed: null,
|
|
1005
|
+
path: providerAgentProfilesDestination(target.providerId, target.scope, context.roots),
|
|
1006
|
+
localModifications: 'unknown',
|
|
1007
|
+
problems: [err.message],
|
|
1008
|
+
};
|
|
1009
|
+
}
|
|
1010
|
+
}
|
|
1011
|
+
const combinedState = combineInstalledState(status.state, agentProfiles?.state);
|
|
1012
|
+
const profileProblems = (agentProfiles?.problems || []).map((problem) => `agent profiles: ${problem}`);
|
|
851
1013
|
rows.push({
|
|
852
1014
|
provider: target.providerId,
|
|
853
1015
|
providerName: target.provider.displayName,
|
|
@@ -855,9 +1017,10 @@ async function inspectCommand(command, args, options = {}) {
|
|
|
855
1017
|
path: target.destination,
|
|
856
1018
|
installed: status.installed,
|
|
857
1019
|
available: packageInfo.version,
|
|
858
|
-
state:
|
|
859
|
-
localModifications: status.localModifications,
|
|
860
|
-
problems: status.problems,
|
|
1020
|
+
state: combinedState,
|
|
1021
|
+
localModifications: status.localModifications === 'present' || agentProfiles?.localModifications === 'present' ? 'present' : status.localModifications,
|
|
1022
|
+
problems: [...status.problems, ...profileProblems],
|
|
1023
|
+
agentProfiles,
|
|
861
1024
|
symlink: Boolean(status.symlink),
|
|
862
1025
|
});
|
|
863
1026
|
}
|
|
@@ -865,9 +1028,18 @@ async function inspectCommand(command, args, options = {}) {
|
|
|
865
1028
|
return { ok: true, command, providers: rows, exitCode: args.failOnUpdate && hasUpdate ? EXIT_CODES.updateAvailable : EXIT_CODES.success };
|
|
866
1029
|
}
|
|
867
1030
|
|
|
1031
|
+
function combineInstalledState(skillState, profileState) {
|
|
1032
|
+
if (!profileState || profileState === 'current') return skillState;
|
|
1033
|
+
if (skillState !== 'current') return skillState;
|
|
1034
|
+
if (profileState === 'not installed') return 'partial';
|
|
1035
|
+
if (profileState === 'update available') return 'update available';
|
|
1036
|
+
return profileState;
|
|
1037
|
+
}
|
|
1038
|
+
|
|
868
1039
|
export async function doctorCommand(args, options = {}) {
|
|
869
1040
|
const context = resolveTargets('doctor', args, options);
|
|
870
1041
|
const packageInfo = loadPackageInfo(context.roots.packageRoot);
|
|
1042
|
+
const capabilities = await runtimeCapabilities(options);
|
|
871
1043
|
const checks = [];
|
|
872
1044
|
const nodeMajor = Number.parseInt(process.versions.node.split('.')[0], 10);
|
|
873
1045
|
checks.push({ level: nodeMajor >= 20 ? 'PASS' : 'FAIL', message: `Node.js ${process.versions.node}` });
|
|
@@ -902,7 +1074,81 @@ export async function doctorCommand(args, options = {}) {
|
|
|
902
1074
|
}
|
|
903
1075
|
|
|
904
1076
|
const hasFail = checks.some((check) => check.level === 'FAIL');
|
|
905
|
-
return { ok: !hasFail, command: 'doctor', checks, exitCode: hasFail ? EXIT_CODES.integrityFailure : EXIT_CODES.success };
|
|
1077
|
+
return { ok: !hasFail, command: 'doctor', checks, capabilities, exitCode: hasFail ? EXIT_CODES.integrityFailure : EXIT_CODES.success };
|
|
1078
|
+
}
|
|
1079
|
+
|
|
1080
|
+
async function optionalDependencyAvailable(name, options = {}) {
|
|
1081
|
+
if (options.capabilityResolver) return Boolean(await options.capabilityResolver(name));
|
|
1082
|
+
try {
|
|
1083
|
+
await import(name);
|
|
1084
|
+
return true;
|
|
1085
|
+
} catch {
|
|
1086
|
+
return false;
|
|
1087
|
+
}
|
|
1088
|
+
}
|
|
1089
|
+
|
|
1090
|
+
export async function runtimeCapabilities(options = {}) {
|
|
1091
|
+
const env = options.env || process.env;
|
|
1092
|
+
const dependencyDefinitions = [
|
|
1093
|
+
{
|
|
1094
|
+
id: 'browser_render',
|
|
1095
|
+
requirements: ['playwright'],
|
|
1096
|
+
readyLimitations: ['dependency presence does not establish browser launch readiness'],
|
|
1097
|
+
},
|
|
1098
|
+
{
|
|
1099
|
+
id: 'lighthouse_lab',
|
|
1100
|
+
requirements: ['lighthouse', 'chrome-launcher'],
|
|
1101
|
+
readyLimitations: ['dependency presence does not establish Chrome launch readiness or a successful lab run'],
|
|
1102
|
+
},
|
|
1103
|
+
{
|
|
1104
|
+
id: 'ocr',
|
|
1105
|
+
requirements: ['tesseract.js'],
|
|
1106
|
+
readyLimitations: ['dependency presence does not establish OCR accuracy or source-image availability'],
|
|
1107
|
+
},
|
|
1108
|
+
];
|
|
1109
|
+
const capabilities = [];
|
|
1110
|
+
for (const definition of dependencyDefinitions) {
|
|
1111
|
+
const availability = await Promise.all(definition.requirements.map(async (name) => ({
|
|
1112
|
+
name,
|
|
1113
|
+
available: await optionalDependencyAvailable(name, options),
|
|
1114
|
+
})));
|
|
1115
|
+
const missing = availability.filter((item) => !item.available).map((item) => item.name);
|
|
1116
|
+
capabilities.push({
|
|
1117
|
+
id: definition.id,
|
|
1118
|
+
state: missing.length ? 'missing_dependency' : 'ready',
|
|
1119
|
+
requirements: definition.requirements,
|
|
1120
|
+
limitations: missing.length
|
|
1121
|
+
? missing.map((name) => `optional dependency ${name} is not installed`)
|
|
1122
|
+
: definition.readyLimitations,
|
|
1123
|
+
});
|
|
1124
|
+
}
|
|
1125
|
+
|
|
1126
|
+
for (const definition of [
|
|
1127
|
+
{
|
|
1128
|
+
id: 'crux_field',
|
|
1129
|
+
credential: 'CRUX_API_KEY',
|
|
1130
|
+
readyLimitation: 'credential presence does not establish validity, scope, quota, or API availability',
|
|
1131
|
+
},
|
|
1132
|
+
{
|
|
1133
|
+
id: 'gsc_index',
|
|
1134
|
+
credential: 'GSC_ACCESS_TOKEN',
|
|
1135
|
+
readyLimitation: 'credential presence does not establish validity, scope, property access, or API availability',
|
|
1136
|
+
},
|
|
1137
|
+
{
|
|
1138
|
+
id: 'ga4_metrics',
|
|
1139
|
+
credential: 'GA4_ACCESS_TOKEN',
|
|
1140
|
+
readyLimitation: 'credential presence does not establish validity, scope, property access, or API availability',
|
|
1141
|
+
},
|
|
1142
|
+
]) {
|
|
1143
|
+
const configured = typeof env[definition.credential] === 'string' && env[definition.credential].length > 0;
|
|
1144
|
+
capabilities.push({
|
|
1145
|
+
id: definition.id,
|
|
1146
|
+
state: configured ? 'ready' : 'missing_credential',
|
|
1147
|
+
requirements: [definition.credential],
|
|
1148
|
+
limitations: [configured ? definition.readyLimitation : `${definition.credential} is not configured`],
|
|
1149
|
+
});
|
|
1150
|
+
}
|
|
1151
|
+
return capabilities;
|
|
906
1152
|
}
|
|
907
1153
|
|
|
908
1154
|
function inspectInstallationWithOptionalBundle(target, packageRootPath, version) {
|
|
@@ -1103,7 +1349,15 @@ function renderList(result) {
|
|
|
1103
1349
|
}
|
|
1104
1350
|
|
|
1105
1351
|
function renderDoctor(result) {
|
|
1106
|
-
|
|
1352
|
+
const lines = ['Citable doctor', '', ...result.checks.map((check) => `${check.level.padEnd(5)} ${check.message}`)];
|
|
1353
|
+
if (result.capabilities?.length) {
|
|
1354
|
+
lines.push('', 'Runtime capabilities');
|
|
1355
|
+
for (const capability of result.capabilities) {
|
|
1356
|
+
lines.push(` ${capability.id.padEnd(18)} ${capability.state}`);
|
|
1357
|
+
for (const limitation of capability.limitations) lines.push(` ${limitation}`);
|
|
1358
|
+
}
|
|
1359
|
+
}
|
|
1360
|
+
return lines.join('\n');
|
|
1107
1361
|
}
|
|
1108
1362
|
|
|
1109
1363
|
export function installerHelp() {
|
|
@@ -7,8 +7,10 @@ export const PROVIDERS = Object.freeze({
|
|
|
7
7
|
aliases: ['claude-code', 'claudecode'],
|
|
8
8
|
projectDir: '.claude',
|
|
9
9
|
projectSkillsDir: '.claude/skills',
|
|
10
|
+
projectAgentProfilesDir: '.claude/agents',
|
|
10
11
|
globalHints: ['.claude'],
|
|
11
12
|
globalSkillsDir: '.claude/skills',
|
|
13
|
+
globalAgentProfilesDir: '.claude/agents',
|
|
12
14
|
},
|
|
13
15
|
codex: {
|
|
14
16
|
id: 'codex',
|
|
@@ -162,3 +164,12 @@ export function providerBundleSkillPath(providerId) {
|
|
|
162
164
|
if (!provider) throw new Error(`unknown provider: ${providerId}`);
|
|
163
165
|
return path.join('dist', 'universal', provider.projectSkillsDir, 'citable');
|
|
164
166
|
}
|
|
167
|
+
|
|
168
|
+
export function providerAgentProfilesDestination(providerId, scope, roots) {
|
|
169
|
+
const provider = PROVIDERS[providerId];
|
|
170
|
+
if (!provider) throw new Error(`unknown provider: ${providerId}`);
|
|
171
|
+
const dir = scope === 'project' ? provider.projectAgentProfilesDir : provider.globalAgentProfilesDir;
|
|
172
|
+
if (!dir) return null;
|
|
173
|
+
const root = scope === 'project' ? roots.projectRoot : roots.home;
|
|
174
|
+
return path.join(root, dir, 'citable');
|
|
175
|
+
}
|
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
import { envelope, observationRun, readInput } from './common.js';
|
|
2
|
-
import { fetchUrl } from '../crawler/fetch.js';
|
|
2
|
+
import { fetchUrl, handlePublicBrowserRoute, validatePublicUrl } from '../crawler/fetch.js';
|
|
3
3
|
import { sha256 } from '../shared/io.js';
|
|
4
4
|
import { validateAgainst } from '../shared/schemaValidator.js';
|
|
5
|
+
import { analyzeRenderParity } from './renderParity.js';
|
|
5
6
|
|
|
6
|
-
async function executeStep(page, step) {
|
|
7
|
+
async function executeStep(page, step, lookup) {
|
|
7
8
|
const locator = step.locator ? page.locator(step.locator).first() : null;
|
|
8
|
-
if (step.action === 'navigate')
|
|
9
|
+
if (step.action === 'navigate') {
|
|
10
|
+
await validatePublicUrl(step.url, lookup ? { lookup } : {});
|
|
11
|
+
await page.goto(step.url, { waitUntil: 'networkidle' });
|
|
12
|
+
}
|
|
9
13
|
else if (step.action === 'click') await locator.click();
|
|
10
14
|
else if (step.action === 'fill') {
|
|
11
15
|
const value = process.env[step.value_env];
|
|
@@ -22,7 +26,7 @@ async function executeStep(page, step) {
|
|
|
22
26
|
else if (step.action === 'scroll') await locator.scrollIntoViewIfNeeded();
|
|
23
27
|
}
|
|
24
28
|
|
|
25
|
-
async function playwrightCapture(profile, plan, playwright) {
|
|
29
|
+
async function playwrightCapture(profile, plan, playwright, lookup) {
|
|
26
30
|
if (profile.authentication_state !== 'anonymous') throw new Error(`authentication state ${profile.authentication_state} requires a disclosed custom adapter`);
|
|
27
31
|
const browserType = playwright[profile.browser.engine];
|
|
28
32
|
if (!browserType) throw new Error(`Playwright engine ${profile.browser.engine} is unavailable`);
|
|
@@ -35,6 +39,7 @@ async function playwrightCapture(profile, plan, playwright) {
|
|
|
35
39
|
locale: profile.locale,
|
|
36
40
|
});
|
|
37
41
|
try {
|
|
42
|
+
await context.route('**/*', (route) => handlePublicBrowserRoute(route, lookup ? { lookup } : {}));
|
|
38
43
|
const page = await context.newPage();
|
|
39
44
|
const consoleErrors = [], networkFailures = [], steps = [], stepScreenshots = {};
|
|
40
45
|
page.on('console', (message) => { if (message.type() === 'error') consoleErrors.push(message.text()); });
|
|
@@ -43,7 +48,7 @@ async function playwrightCapture(profile, plan, playwright) {
|
|
|
43
48
|
for (const step of profile.steps) {
|
|
44
49
|
const record = { step_id: step.step_id, action: step.action, status: 'completed', failure: null, screenshot_ref: null };
|
|
45
50
|
try {
|
|
46
|
-
await executeStep(page, step);
|
|
51
|
+
await executeStep(page, step, lookup);
|
|
47
52
|
if (step.capture_screenshot) {
|
|
48
53
|
record.screenshot_ref = `journeys/${profile.profile_id}/steps/${step.step_id}.png`;
|
|
49
54
|
stepScreenshots[record.screenshot_ref] = await page.screenshot({ fullPage: true });
|
|
@@ -79,6 +84,7 @@ export async function observeBrowserPlan(root, options) {
|
|
|
79
84
|
const check = validateAgainst('browser-evidence-plan.schema.json', plan);
|
|
80
85
|
if (!check.valid) throw new Error(`browser evidence plan violates contract: ${check.errors.join('; ')}`);
|
|
81
86
|
if (options.target && options.target !== plan.target) throw new Error('browser evidence target differs from the plan target');
|
|
87
|
+
await validatePublicUrl(plan.target, options.lookup ? { lookup: options.lookup } : {});
|
|
82
88
|
const profileIds = plan.profiles.map((item) => item.profile_id);
|
|
83
89
|
if (new Set(profileIds).size !== profileIds.length) throw new Error('browser evidence plan contains duplicate profile ids');
|
|
84
90
|
const targetOrigin = new URL(plan.target).origin;
|
|
@@ -113,7 +119,7 @@ export async function observeBrowserPlan(root, options) {
|
|
|
113
119
|
try {
|
|
114
120
|
const capture = options.captureJourney
|
|
115
121
|
? await options.captureJourney(profile, plan)
|
|
116
|
-
: await playwrightCapture(profile, plan, playwright);
|
|
122
|
+
: await playwrightCapture(profile, plan, playwright, options.lookup);
|
|
117
123
|
if (!capture || typeof capture.dom !== 'string' || typeof capture.text !== 'string' || typeof capture.browser_version !== 'string' || capture.screenshot == null) {
|
|
118
124
|
throw new Error('browser adapter returned an incomplete capture contract');
|
|
119
125
|
}
|
|
@@ -137,6 +143,7 @@ export async function observeBrowserPlan(root, options) {
|
|
|
137
143
|
artifacts[name] = value;
|
|
138
144
|
}
|
|
139
145
|
const versionMatches = profile.browser.expected_version == null || profile.browser.expected_version === capture.browser_version;
|
|
146
|
+
const renderParity = analyzeRenderParity(initial.body, capture.dom, plan.semantic_impact_policy || null);
|
|
140
147
|
const data = {
|
|
141
148
|
plan_id: plan.plan_id, target: plan.target, profile_id: profile.profile_id,
|
|
142
149
|
browser: { ...profile.browser, observed_version: capture.browser_version, version_matches_expectation: versionMatches },
|
|
@@ -144,9 +151,10 @@ export async function observeBrowserPlan(root, options) {
|
|
|
144
151
|
consent_state: profile.consent_state, authentication_state: profile.authentication_state,
|
|
145
152
|
final_url: capture.final_url, status: capture.status, dom_hash: sha256(capture.dom), text_hash: sha256(capture.text),
|
|
146
153
|
steps: capture.steps || [], artifact_refs: refs,
|
|
154
|
+
render_parity: renderParity,
|
|
147
155
|
interpretation_boundary: 'Observed differences do not establish semantic, retrieval, citation, ranking, or business impact.',
|
|
148
156
|
};
|
|
149
|
-
const limitations = [...plan.limitations, ...profile.limitations];
|
|
157
|
+
const limitations = [...plan.limitations, ...profile.limitations, ...(plan.semantic_impact_policy?.limitations || [])];
|
|
150
158
|
if (!versionMatches) limitations.push('Observed browser version differs from the planned version.');
|
|
151
159
|
observations.push(envelope('browser_journey', data, { method: 'browser', source: `playwright/${profile.browser.engine}`, raw: capture.dom, limitations }));
|
|
152
160
|
} catch (error) {
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { substantiate } from '../commands/substantiate.js';
|
|
2
|
+
import { loadRegistries } from '../registries/index.js';
|
|
3
|
+
|
|
4
|
+
function keyTerms(text) {
|
|
5
|
+
return (String(text || '').toLowerCase().match(/[a-z][a-z-]{4,}/g) || []).slice(0, 8);
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
function escapeRegExp(s) {
|
|
9
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function overlapScore(assertionText, claimText) {
|
|
13
|
+
const terms = keyTerms(assertionText);
|
|
14
|
+
if (terms.length < 3) return 0;
|
|
15
|
+
const target = String(claimText || '').toLowerCase();
|
|
16
|
+
// Whole-word boundaries only — a raw substring test would match "test" inside
|
|
17
|
+
// "greatest" and misassign a substantiation status to the wrong claim.
|
|
18
|
+
const present = terms.filter((t) => new RegExp(`\\b${escapeRegExp(t)}\\b`).test(target)).length;
|
|
19
|
+
return present / terms.length;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function surfaceMatches(citationUrl, surface) {
|
|
23
|
+
try {
|
|
24
|
+
const url = new URL(citationUrl);
|
|
25
|
+
// Path-boundary comparison, not raw substring — a query string or fragment
|
|
26
|
+
// containing the surface text must not count as a match.
|
|
27
|
+
return url.pathname === surface || url.pathname.startsWith(surface.endsWith('/') ? surface : `${surface}/`);
|
|
28
|
+
} catch {
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** Find the best-matching registry claim for one AI answer assertion (token-overlap heuristic, adapted from CLAIM-008). */
|
|
34
|
+
export function matchClaim(assertionText, claims, citationUrl) {
|
|
35
|
+
let best = null;
|
|
36
|
+
let bestScore = 0;
|
|
37
|
+
for (const c of claims.filter((c) => c.status !== 'retired')) {
|
|
38
|
+
let score = overlapScore(assertionText, c.claim);
|
|
39
|
+
if (citationUrl && (c.publication_surfaces || []).some((s) => surfaceMatches(citationUrl, s))) score += 0.1;
|
|
40
|
+
if (score > bestScore) {
|
|
41
|
+
bestScore = score;
|
|
42
|
+
best = c;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return bestScore >= 0.4 ? best : null;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const OUTCOME_TO_STATUS = {
|
|
49
|
+
verified: 'confirmed',
|
|
50
|
+
verified_narrowed: 'confirmed',
|
|
51
|
+
contradicted: 'contradicted',
|
|
52
|
+
expired: 'stale',
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Diff one answer-engine assertion against the caller's own claim/evidence registry.
|
|
57
|
+
* Never asserts the AI is wrong — only confirmed/contradicted/stale/unsupported relative
|
|
58
|
+
* to the caller's own evidence. Never implies a live query happened.
|
|
59
|
+
*/
|
|
60
|
+
export function diffAssertion(assertionText, citationUrl, { claims, assessmentByClaimId }) {
|
|
61
|
+
if (!assertionText) return null;
|
|
62
|
+
const matched = matchClaim(assertionText, claims, citationUrl);
|
|
63
|
+
if (!matched) {
|
|
64
|
+
return { status: 'unsupported', matched_claim_id: null, reasons: ['no claim in your registry covers this assertion; it could not be checked'] };
|
|
65
|
+
}
|
|
66
|
+
const assessment = assessmentByClaimId.get(matched.claim_id);
|
|
67
|
+
const status = OUTCOME_TO_STATUS[assessment?.outcome] ?? 'unsupported';
|
|
68
|
+
return { status, matched_claim_id: matched.claim_id, reasons: assessment?.reasons ?? [] };
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Precompute the shared claim/evidence lookups once per `observe citations` run.
|
|
73
|
+
* `problems` surfaces registry schema-validation issues (malformed but parseable
|
|
74
|
+
* YAML) so the caller can report them as warnings rather than silently diffing
|
|
75
|
+
* against data that failed its own contract.
|
|
76
|
+
*/
|
|
77
|
+
export function buildClaimDiffContext(root, refDate) {
|
|
78
|
+
const { registries, problems } = loadRegistries(root);
|
|
79
|
+
const claims = Array.isArray(registries.claims?.entries) ? registries.claims.entries : [];
|
|
80
|
+
const { assessments } = substantiate(root, { write: false, refDate });
|
|
81
|
+
const assessmentByClaimId = new Map(assessments.map((a) => [a.claim_id, a]));
|
|
82
|
+
return { claims, assessmentByClaimId, problems };
|
|
83
|
+
}
|
|
@@ -42,6 +42,8 @@ export function summarizeObservations(items) {
|
|
|
42
42
|
for (const item of items) { byKind[item.kind] = (byKind[item.kind] || 0) + 1; byState[item.state] = (byState[item.state] || 0) + 1; }
|
|
43
43
|
const citations = items.filter((item) => item.kind === 'citation');
|
|
44
44
|
const reviews = items.filter((item) => item.kind === 'citation_review');
|
|
45
|
+
const claimDiffCounts = { confirmed: 0, contradicted: 0, stale: 0, unsupported: 0, not_checked: 0 };
|
|
46
|
+
for (const review of reviews) claimDiffCounts[review.data.claim_diff?.status ?? 'not_checked']++;
|
|
45
47
|
const providers = {};
|
|
46
48
|
for (const item of citations) {
|
|
47
49
|
const provider = item.data.provider || 'unknown';
|
|
@@ -49,6 +51,17 @@ export function summarizeObservations(items) {
|
|
|
49
51
|
providers[provider].runs++;
|
|
50
52
|
if (item.data.property_cited) providers[provider].property_cited++;
|
|
51
53
|
}
|
|
54
|
+
const competitiveSourceMap = new Map();
|
|
55
|
+
for (const review of reviews.filter((item) => !item.data.first_party)) {
|
|
56
|
+
let domain;
|
|
57
|
+
try { domain = new URL(review.data.canonical_url).hostname; } catch { continue; }
|
|
58
|
+
const current = competitiveSourceMap.get(domain) || { domain, citation_count: 0, declared_profiles: [] };
|
|
59
|
+
current.citation_count++;
|
|
60
|
+
if (review.data.source_profile && !current.declared_profiles.some((item) => JSON.stringify(item) === JSON.stringify(review.data.source_profile))) {
|
|
61
|
+
current.declared_profiles.push(review.data.source_profile);
|
|
62
|
+
}
|
|
63
|
+
competitiveSourceMap.set(domain, current);
|
|
64
|
+
}
|
|
52
65
|
return {
|
|
53
66
|
total: items.length, by_kind: byKind, by_state: byState,
|
|
54
67
|
citation_metrics: citations.length ? {
|
|
@@ -56,8 +69,10 @@ export function summarizeObservations(items) {
|
|
|
56
69
|
citation_presence_rate: citations.filter((x) => x.data.property_cited).length / citations.length,
|
|
57
70
|
supported_citation_rate: reviews.length ? reviews.filter((x) => x.data.support_status === 'supported').length / reviews.length : null,
|
|
58
71
|
review_required: reviews.filter((x) => x.state === 'review_required').length,
|
|
72
|
+
claim_diff: claimDiffCounts,
|
|
59
73
|
provider_results: providers,
|
|
60
74
|
competitive_domains: [...new Set(reviews.filter((x) => !x.data.first_party).map((x) => { try { return new URL(x.data.canonical_url).hostname; } catch { return null; } }).filter(Boolean))].sort(),
|
|
75
|
+
competitive_sources: [...competitiveSourceMap.values()].sort((a, b) => a.domain.localeCompare(b.domain)),
|
|
61
76
|
} : null,
|
|
62
77
|
};
|
|
63
78
|
}
|