create-hq 5.0.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/dist/deps.d.ts +4 -0
- package/dist/deps.d.ts.map +1 -0
- package/dist/deps.js +65 -0
- package/dist/deps.js.map +1 -0
- package/dist/git.d.ts +3 -0
- package/dist/git.d.ts.map +1 -0
- package/dist/git.js +19 -0
- package/dist/git.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +23 -0
- package/dist/index.js.map +1 -0
- package/dist/scaffold.d.ts +8 -0
- package/dist/scaffold.d.ts.map +1 -0
- package/dist/scaffold.js +130 -0
- package/dist/scaffold.js.map +1 -0
- package/dist/ui.d.ts +7 -0
- package/dist/ui.d.ts.map +1 -0
- package/dist/ui.js +36 -0
- package/dist/ui.js.map +1 -0
- package/package.json +41 -0
- package/template/.claude/CLAUDE.md +202 -0
- package/template/.claude/commands/checkpoint.md +127 -0
- package/template/.claude/commands/cleanup.md +307 -0
- package/template/.claude/commands/execute-task.md +440 -0
- package/template/.claude/commands/exit-plan.md +41 -0
- package/template/.claude/commands/handoff.md +97 -0
- package/template/.claude/commands/learn.md +218 -0
- package/template/.claude/commands/metrics.md +118 -0
- package/template/.claude/commands/newworker.md +162 -0
- package/template/.claude/commands/nexttask.md +67 -0
- package/template/.claude/commands/prd.md +238 -0
- package/template/.claude/commands/reanchor.md +51 -0
- package/template/.claude/commands/remember.md +126 -0
- package/template/.claude/commands/run-project.md +348 -0
- package/template/.claude/commands/run.md +110 -0
- package/template/.claude/commands/search-reindex.md +62 -0
- package/template/.claude/commands/search.md +100 -0
- package/template/.claude/commands/setup.md +381 -0
- package/template/.claude/scripts/pure-ralph-loop.ps1 +312 -0
- package/template/.claude/scripts/pure-ralph-loop.sh +859 -0
- package/template/CHANGELOG.md +220 -0
- package/template/LICENSE +21 -0
- package/template/MIGRATION.md +259 -0
- package/template/README.md +368 -0
- package/template/data/journal/.gitkeep +0 -0
- package/template/docs/images/ascii-banner-options.md +122 -0
- package/template/docs/images/hq-banner.svg +105 -0
- package/template/knowledge/Ralph/01-overview.md +71 -0
- package/template/knowledge/Ralph/02-core-concepts.md +114 -0
- package/template/knowledge/Ralph/03-how-ralph-works.md +184 -0
- package/template/knowledge/Ralph/04-back-pressure.md +222 -0
- package/template/knowledge/Ralph/05-specifications.md +210 -0
- package/template/knowledge/Ralph/06-agents-md.md +222 -0
- package/template/knowledge/Ralph/07-implementation.md +316 -0
- package/template/knowledge/Ralph/08-economics.md +182 -0
- package/template/knowledge/Ralph/09-resources.md +145 -0
- package/template/knowledge/Ralph/10-claude-code-workflow.md +212 -0
- package/template/knowledge/Ralph/11-team-training-guide.md +383 -0
- package/template/knowledge/Ralph/README.md +40 -0
- package/template/knowledge/ai-security-framework/CONTRIBUTING.md +139 -0
- package/template/knowledge/ai-security-framework/GLOSSARY.md +176 -0
- package/template/knowledge/ai-security-framework/LICENSE +21 -0
- package/template/knowledge/ai-security-framework/QUICK-START.md +172 -0
- package/template/knowledge/ai-security-framework/README.md +232 -0
- package/template/knowledge/ai-security-framework/checklists/browser-security.md +301 -0
- package/template/knowledge/ai-security-framework/checklists/credential-isolation.md +322 -0
- package/template/knowledge/ai-security-framework/checklists/incident-response.md +288 -0
- package/template/knowledge/ai-security-framework/checklists/pre-flight.md +249 -0
- package/template/knowledge/ai-security-framework/checklists/weekly-audit.md +159 -0
- package/template/knowledge/ai-security-framework/configs/audit-logging.md +372 -0
- package/template/knowledge/ai-security-framework/configs/kill-switches.md +354 -0
- package/template/knowledge/ai-security-framework/docs/01-core-principles.md +256 -0
- package/template/knowledge/ai-security-framework/docs/02-threat-landscape.md +326 -0
- package/template/knowledge/ai-security-framework/docs/03-security-posture.md +250 -0
- package/template/knowledge/ai-security-framework/templates/agents-security.md +233 -0
- package/template/knowledge/design-styles/README.md +42 -0
- package/template/knowledge/design-styles/american-industrial.md +136 -0
- package/template/knowledge/design-styles/ethereal-abstract.md +133 -0
- package/template/knowledge/design-styles/liminal-portal.md +111 -0
- package/template/knowledge/design-styles/swipes/american-industrial/G-3m4YPW0AADdu2.jpeg +0 -0
- package/template/knowledge/design-styles/swipes/american-industrial/G-JJlt5WwAABK3K.png +0 -0
- package/template/knowledge/design-styles/swipes/american-industrial/G-JJmj5W0AEbJ-7.png +0 -0
- package/template/knowledge/design-styles/swipes/american-industrial/G59fgNuXkAAKLJQ (1).jpeg +0 -0
- package/template/knowledge/design-styles/swipes/american-industrial/G59fgNuXkAAKLJQ.jpeg +0 -0
- package/template/knowledge/design-styles/swipes/american-industrial/G7fVkn3WEAAM-ST.jpeg +0 -0
- package/template/knowledge/design-styles/swipes/american-industrial/G8ECO5JWEAIksyn.png +0 -0
- package/template/knowledge/design-styles/swipes/american-industrial/G9-3GQSWoAA8eqZ.png +0 -0
- package/template/knowledge/design-styles/swipes/american-industrial/G9xEOqrXkAEZRcs.png +0 -0
- package/template/knowledge/design-styles/swipes/american-industrial/G_MVeJrXQAA8sx4.jpeg +0 -0
- package/template/knowledge/design-styles/swipes/american-industrial/G_RSkmGXkAAgAVZ.png +0 -0
- package/template/knowledge/design-styles/swipes/american-industrial/README.md +31 -0
- package/template/knowledge/design-styles/swipes/american-industrial/qyqtg7Dq.png +0 -0
- package/template/knowledge/dev-team/README.md +35 -0
- package/template/knowledge/dev-team/patterns/README.md +34 -0
- package/template/knowledge/dev-team/patterns/frontend/react-best-practices.md +178 -0
- package/template/knowledge/dev-team/troubleshooting/README.md +31 -0
- package/template/knowledge/dev-team/workflows/README.md +49 -0
- package/template/knowledge/hq/checkpoint-schema.json +51 -0
- package/template/knowledge/hq/index-md-spec.md +74 -0
- package/template/knowledge/hq/thread-schema.md +153 -0
- package/template/knowledge/hq-core/checkpoint-schema.json +51 -0
- package/template/knowledge/hq-core/index-md-spec.md +74 -0
- package/template/knowledge/hq-core/thread-schema.md +153 -0
- package/template/knowledge/loom/README.md +51 -0
- package/template/knowledge/loom/architecture.md +125 -0
- package/template/knowledge/loom/code-style.md +169 -0
- package/template/knowledge/loom/llm-proxy.md +132 -0
- package/template/knowledge/loom/state-machine.md +131 -0
- package/template/knowledge/loom/thread-system.md +117 -0
- package/template/knowledge/loom/tools.md +94 -0
- package/template/knowledge/loom/weaver.md +96 -0
- package/template/knowledge/loom/web-frontend.md +131 -0
- package/template/knowledge/projects/README.md +72 -0
- package/template/knowledge/projects/templates/README.template.md +28 -0
- package/template/knowledge/workers/README.md +195 -0
- package/template/knowledge/workers/ralph-loop-pattern.md +157 -0
- package/template/knowledge/workers/skill-schema.md +182 -0
- package/template/knowledge/workers/state-machine.md +102 -0
- package/template/knowledge/workers/templates/base-worker.yaml +73 -0
- package/template/knowledge/workers/templates/code-worker.yaml +85 -0
- package/template/knowledge/workers/templates/skill.yaml +49 -0
- package/template/knowledge/workers/templates/social-worker.yaml +70 -0
- package/template/modules/examples/full-manifest.yaml +92 -0
- package/template/modules/examples/minimal.yaml +14 -0
- package/template/modules/modules.yaml +59 -0
- package/template/projects/.gitkeep +0 -0
- package/template/projects/incorporate-workers-into-pure-ralph/prd.json +88 -0
- package/template/projects/pure-ralph-branch-isolation/README.md +114 -0
- package/template/projects/pure-ralph-branch-isolation/prd.json +123 -0
- package/template/projects/purist-ralph-loop/README.md +148 -0
- package/template/projects/purist-ralph-loop/prd.json +135 -0
- package/template/projects/ralph-test/prd.json +50 -0
- package/template/prompts/pure-ralph-base.md +551 -0
- package/template/settings/.gitkeep +0 -0
- package/template/settings/pure-ralph.json +42 -0
- package/template/social-content/drafts/INDEX.md +21 -0
- package/template/social-content/drafts/linkedin/.gitkeep +1 -0
- package/template/social-content/drafts/x/.gitkeep +1 -0
- package/template/social-content/images/.gitkeep +1 -0
- package/template/starter-projects/code-worker/README.md +97 -0
- package/template/starter-projects/code-worker/prd.json +45 -0
- package/template/starter-projects/personal-assistant/README.md +42 -0
- package/template/starter-projects/personal-assistant/prd.json +43 -0
- package/template/starter-projects/social-media/README.md +60 -0
- package/template/starter-projects/social-media/prd.json +43 -0
- package/template/workers/content-brand/README.md +59 -0
- package/template/workers/content-brand/skills/messaging-alignment.md +91 -0
- package/template/workers/content-brand/skills/tone-check.md +76 -0
- package/template/workers/content-brand/skills/voice-analysis.md +68 -0
- package/template/workers/content-brand/worker.yaml +81 -0
- package/template/workers/content-legal/README.md +80 -0
- package/template/workers/content-legal/skills/claim-substantiation.md +150 -0
- package/template/workers/content-legal/skills/compliance-scan.md +123 -0
- package/template/workers/content-legal/skills/disclaimer-check.md +146 -0
- package/template/workers/content-legal/worker.yaml +118 -0
- package/template/workers/content-product/README.md +77 -0
- package/template/workers/content-product/skills/claim-verification.md +96 -0
- package/template/workers/content-product/skills/feature-accuracy.md +117 -0
- package/template/workers/content-product/skills/stats-check.md +128 -0
- package/template/workers/content-product/worker.yaml +97 -0
- package/template/workers/content-sales/README.md +70 -0
- package/template/workers/content-sales/skills/conversion-analysis.md +96 -0
- package/template/workers/content-sales/skills/cta-audit.md +107 -0
- package/template/workers/content-sales/skills/value-prop-check.md +114 -0
- package/template/workers/content-sales/worker.yaml +93 -0
- package/template/workers/content-shared/cli.ts +242 -0
- package/template/workers/content-shared/index.ts +234 -0
- package/template/workers/content-shared/lib/accuracy-analyzer.ts +661 -0
- package/template/workers/content-shared/lib/analyze.ts +370 -0
- package/template/workers/content-shared/lib/brand-analyzer.ts +526 -0
- package/template/workers/content-shared/lib/cms-integration.ts +446 -0
- package/template/workers/content-shared/lib/compliance-analyzer.ts +655 -0
- package/template/workers/content-shared/lib/conversion-analyzer.ts +555 -0
- package/template/workers/content-shared/lib/github-integration.ts +582 -0
- package/template/workers/content-shared/lib/output.ts +373 -0
- package/template/workers/content-shared/lib/parser.ts +771 -0
- package/template/workers/content-shared/lib/priority.ts +439 -0
- package/template/workers/content-shared/lib/recommendations.ts +512 -0
- package/template/workers/content-shared/lib/reporter.ts +749 -0
- package/template/workers/content-shared/lib/restructure.ts +664 -0
- package/template/workers/content-shared/lib/scorer.ts +140 -0
- package/template/workers/content-shared/lib/types.ts +227 -0
- package/template/workers/content-shared/lib/variants.ts +595 -0
- package/template/workers/content-shared/package.json +51 -0
- package/template/workers/content-shared/pnpm-lock.yaml +39 -0
- package/template/workers/content-shared/test/sample-page.json +115 -0
- package/template/workers/content-shared/tsconfig.json +20 -0
- package/template/workers/dev-team/README.md +166 -0
- package/template/workers/dev-team/_template.yaml +70 -0
- package/template/workers/dev-team/architect/package.json +27 -0
- package/template/workers/dev-team/architect/skills/api-design.md +89 -0
- package/template/workers/dev-team/architect/skills/refactor-plan.md +96 -0
- package/template/workers/dev-team/architect/skills/system-design.md +100 -0
- package/template/workers/dev-team/architect/src/index.ts +49 -0
- package/template/workers/dev-team/architect/src/mcp-server.ts +122 -0
- package/template/workers/dev-team/architect/src/skills/api-design.ts +316 -0
- package/template/workers/dev-team/architect/src/skills/refactor-plan.ts +264 -0
- package/template/workers/dev-team/architect/src/skills/system-design.ts +212 -0
- package/template/workers/dev-team/architect/tsconfig.json +19 -0
- package/template/workers/dev-team/architect/worker.yaml +128 -0
- package/template/workers/dev-team/backend-dev/package-lock.json +1252 -0
- package/template/workers/dev-team/backend-dev/package.json +27 -0
- package/template/workers/dev-team/backend-dev/skills/implement-endpoint.md +70 -0
- package/template/workers/dev-team/backend-dev/skills/implement-service.md +62 -0
- package/template/workers/dev-team/backend-dev/src/index.ts +51 -0
- package/template/workers/dev-team/backend-dev/src/mcp-server.ts +109 -0
- package/template/workers/dev-team/backend-dev/src/skills/implement-endpoint.ts +122 -0
- package/template/workers/dev-team/backend-dev/src/skills/implement-service.ts +126 -0
- package/template/workers/dev-team/backend-dev/tsconfig.json +19 -0
- package/template/workers/dev-team/backend-dev/worker.yaml +128 -0
- package/template/workers/dev-team/code-reviewer/package-lock.json +1080 -0
- package/template/workers/dev-team/code-reviewer/package.json +24 -0
- package/template/workers/dev-team/code-reviewer/skills/merge-to-production.md +61 -0
- package/template/workers/dev-team/code-reviewer/skills/merge-to-staging.md +54 -0
- package/template/workers/dev-team/code-reviewer/skills/request-changes.md +63 -0
- package/template/workers/dev-team/code-reviewer/skills/review-pr.md +77 -0
- package/template/workers/dev-team/code-reviewer/src/index.ts +56 -0
- package/template/workers/dev-team/code-reviewer/src/mcp-server.ts +101 -0
- package/template/workers/dev-team/code-reviewer/tsconfig.json +19 -0
- package/template/workers/dev-team/code-reviewer/worker.yaml +90 -0
- package/template/workers/dev-team/database-dev/package.json +22 -0
- package/template/workers/dev-team/database-dev/skills/create-schema.md +48 -0
- package/template/workers/dev-team/database-dev/src/index.ts +50 -0
- package/template/workers/dev-team/database-dev/src/mcp-server.ts +76 -0
- package/template/workers/dev-team/database-dev/tsconfig.json +18 -0
- package/template/workers/dev-team/database-dev/worker.yaml +90 -0
- package/template/workers/dev-team/frontend-dev/package.json +22 -0
- package/template/workers/dev-team/frontend-dev/skills/create-component.md +26 -0
- package/template/workers/dev-team/frontend-dev/src/index.ts +50 -0
- package/template/workers/dev-team/frontend-dev/src/mcp-server.ts +77 -0
- package/template/workers/dev-team/frontend-dev/tsconfig.json +18 -0
- package/template/workers/dev-team/frontend-dev/worker.yaml +132 -0
- package/template/workers/dev-team/infra-dev/package.json +24 -0
- package/template/workers/dev-team/infra-dev/skills/add-monitoring.md +73 -0
- package/template/workers/dev-team/infra-dev/skills/configure-deployment.md +80 -0
- package/template/workers/dev-team/infra-dev/skills/create-dockerfile.md +62 -0
- package/template/workers/dev-team/infra-dev/skills/setup-cicd.md +63 -0
- package/template/workers/dev-team/infra-dev/src/index.ts +55 -0
- package/template/workers/dev-team/infra-dev/src/mcp-server.ts +82 -0
- package/template/workers/dev-team/infra-dev/tsconfig.json +19 -0
- package/template/workers/dev-team/infra-dev/worker.yaml +92 -0
- package/template/workers/dev-team/knowledge-curator/package.json +24 -0
- package/template/workers/dev-team/knowledge-curator/skills/curate-troubleshooting.md +63 -0
- package/template/workers/dev-team/knowledge-curator/skills/process-learnings.md +61 -0
- package/template/workers/dev-team/knowledge-curator/skills/sync-documentation.md +76 -0
- package/template/workers/dev-team/knowledge-curator/skills/update-patterns.md +63 -0
- package/template/workers/dev-team/knowledge-curator/src/index.ts +53 -0
- package/template/workers/dev-team/knowledge-curator/src/mcp-server.ts +92 -0
- package/template/workers/dev-team/knowledge-curator/tsconfig.json +19 -0
- package/template/workers/dev-team/knowledge-curator/worker.yaml +80 -0
- package/template/workers/dev-team/motion-designer/package.json +22 -0
- package/template/workers/dev-team/motion-designer/skills/add-animation.md +25 -0
- package/template/workers/dev-team/motion-designer/skills/generate-image.md +36 -0
- package/template/workers/dev-team/motion-designer/src/index.ts +63 -0
- package/template/workers/dev-team/motion-designer/src/mcp-server.ts +79 -0
- package/template/workers/dev-team/motion-designer/tsconfig.json +18 -0
- package/template/workers/dev-team/motion-designer/worker.yaml +84 -0
- package/template/workers/dev-team/product-planner/queue.json +4 -0
- package/template/workers/dev-team/product-planner/worker.yaml +220 -0
- package/template/workers/dev-team/project-manager/package-lock.json +1252 -0
- package/template/workers/dev-team/project-manager/package.json +27 -0
- package/template/workers/dev-team/project-manager/skills/create-prd.md +66 -0
- package/template/workers/dev-team/project-manager/skills/next-issue.md +51 -0
- package/template/workers/dev-team/project-manager/skills/project-status.md +59 -0
- package/template/workers/dev-team/project-manager/skills/update-learnings.md +65 -0
- package/template/workers/dev-team/project-manager/src/index.ts +54 -0
- package/template/workers/dev-team/project-manager/src/mcp-server.ts +207 -0
- package/template/workers/dev-team/project-manager/src/skills/create-prd.ts +86 -0
- package/template/workers/dev-team/project-manager/src/skills/next-issue.ts +137 -0
- package/template/workers/dev-team/project-manager/src/skills/project-status.ts +131 -0
- package/template/workers/dev-team/project-manager/src/skills/update-learnings.ts +94 -0
- package/template/workers/dev-team/project-manager/tsconfig.json +19 -0
- package/template/workers/dev-team/project-manager/worker.yaml +96 -0
- package/template/workers/dev-team/qa-tester/package.json +24 -0
- package/template/workers/dev-team/qa-tester/skills/create-demo-account.md +36 -0
- package/template/workers/dev-team/qa-tester/skills/run-tests.md +36 -0
- package/template/workers/dev-team/qa-tester/skills/write-test.md +27 -0
- package/template/workers/dev-team/qa-tester/src/index.ts +61 -0
- package/template/workers/dev-team/qa-tester/src/mcp-server.ts +88 -0
- package/template/workers/dev-team/qa-tester/tsconfig.json +18 -0
- package/template/workers/dev-team/qa-tester/worker.yaml +116 -0
- package/template/workers/dev-team/task-executor/package-lock.json +1252 -0
- package/template/workers/dev-team/task-executor/package.json +27 -0
- package/template/workers/dev-team/task-executor/skills/analyze-issue.md +101 -0
- package/template/workers/dev-team/task-executor/skills/execute.md +133 -0
- package/template/workers/dev-team/task-executor/skills/report-learnings.md +106 -0
- package/template/workers/dev-team/task-executor/skills/validate-completion.md +121 -0
- package/template/workers/dev-team/task-executor/src/index.ts +54 -0
- package/template/workers/dev-team/task-executor/src/mcp-server.ts +139 -0
- package/template/workers/dev-team/task-executor/src/skills/analyze-issue.ts +219 -0
- package/template/workers/dev-team/task-executor/src/skills/execute.ts +132 -0
- package/template/workers/dev-team/task-executor/src/skills/report-learnings.ts +119 -0
- package/template/workers/dev-team/task-executor/src/skills/validate-completion.ts +142 -0
- package/template/workers/dev-team/task-executor/tsconfig.json +19 -0
- package/template/workers/dev-team/task-executor/worker.yaml +110 -0
- package/template/workers/registry.yaml +171 -0
- package/template/workers/security-scanner/README.md +73 -0
- package/template/workers/security-scanner/skills/pre-deploy-check.md +205 -0
- package/template/workers/security-scanner/worker.yaml +26 -0
- package/template/workspace/checkpoints/.gitkeep +0 -0
- package/template/workspace/content-ideas/inbox.jsonl +0 -0
- package/template/workspace/drafts/.gitkeep +0 -0
- package/template/workspace/learnings/.gitkeep +3 -0
- package/template/workspace/orchestrator/.gitkeep +0 -0
- package/template/workspace/ralph-test/COMPLETE.md +18 -0
- package/template/workspace/ralph-test/hello.txt +2 -0
- package/template/workspace/reports/.gitkeep +0 -0
- package/template/workspace/scratch/.gitkeep +0 -0
- package/template/workspace/threads/.gitkeep +3 -0
|
@@ -0,0 +1,655 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Regulatory Compliance Analyzer (US-010)
|
|
3
|
+
* Checks content for compliance issues, regulated terms, and missing disclaimers
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type {
|
|
7
|
+
AnalysisInput,
|
|
8
|
+
ComplianceAnalysis,
|
|
9
|
+
RegulatedTerm,
|
|
10
|
+
Finding,
|
|
11
|
+
Recommendation,
|
|
12
|
+
} from './types.js';
|
|
13
|
+
|
|
14
|
+
// ============================================
|
|
15
|
+
// Regulated Terms Database
|
|
16
|
+
// ============================================
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Terms that require proof, disclaimers, or carry legal risk
|
|
20
|
+
*/
|
|
21
|
+
export const REGULATED_TERMS: Array<{
|
|
22
|
+
term: string;
|
|
23
|
+
patterns: RegExp[];
|
|
24
|
+
requiresProof: boolean;
|
|
25
|
+
disclaimer?: string;
|
|
26
|
+
risk: 'high' | 'medium' | 'low';
|
|
27
|
+
category: string;
|
|
28
|
+
alternative?: string;
|
|
29
|
+
}> = [
|
|
30
|
+
// Compliance/Security Claims
|
|
31
|
+
{
|
|
32
|
+
term: 'SOC 2',
|
|
33
|
+
patterns: [/\bSOC\s*2\b/gi, /\bSOC\s*II\b/gi, /\bSOC2\b/gi],
|
|
34
|
+
requiresProof: true,
|
|
35
|
+
disclaimer: 'SOC 2 Type I/II certification details available upon request',
|
|
36
|
+
risk: 'high',
|
|
37
|
+
category: 'Compliance',
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
term: 'HIPAA',
|
|
41
|
+
patterns: [/\bHIPAA\b/gi],
|
|
42
|
+
requiresProof: true,
|
|
43
|
+
disclaimer: 'HIPAA compliance applies when used with covered entities under a BAA',
|
|
44
|
+
risk: 'high',
|
|
45
|
+
category: 'Compliance',
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
term: 'GDPR',
|
|
49
|
+
patterns: [/\bGDPR\b/gi],
|
|
50
|
+
requiresProof: true,
|
|
51
|
+
disclaimer: 'GDPR compliance details available in our Data Processing Agreement',
|
|
52
|
+
risk: 'high',
|
|
53
|
+
category: 'Compliance',
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
term: 'PCI DSS',
|
|
57
|
+
patterns: [/\bPCI\s*(?:DSS)?\b/gi, /\bPCI\s*compliant\b/gi],
|
|
58
|
+
requiresProof: true,
|
|
59
|
+
disclaimer: 'PCI DSS compliance level documentation available upon request',
|
|
60
|
+
risk: 'high',
|
|
61
|
+
category: 'Compliance',
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
term: 'ISO 27001',
|
|
65
|
+
patterns: [/\bISO\s*27001\b/gi, /\bISO\s*certification\b/gi],
|
|
66
|
+
requiresProof: true,
|
|
67
|
+
disclaimer: 'ISO 27001 certification documentation available upon request',
|
|
68
|
+
risk: 'high',
|
|
69
|
+
category: 'Compliance',
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
term: 'FedRAMP',
|
|
73
|
+
patterns: [/\bFedRAMP\b/gi],
|
|
74
|
+
requiresProof: true,
|
|
75
|
+
disclaimer: 'FedRAMP authorization status available on marketplace.fedramp.gov',
|
|
76
|
+
risk: 'high',
|
|
77
|
+
category: 'Compliance',
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
term: 'CCPA',
|
|
81
|
+
patterns: [/\bCCPA\b/gi],
|
|
82
|
+
requiresProof: true,
|
|
83
|
+
disclaimer: 'CCPA compliance details available in our Privacy Policy',
|
|
84
|
+
risk: 'medium',
|
|
85
|
+
category: 'Compliance',
|
|
86
|
+
},
|
|
87
|
+
|
|
88
|
+
// Absolute Claims
|
|
89
|
+
{
|
|
90
|
+
term: 'guaranteed',
|
|
91
|
+
patterns: [/\bguarantee[ds]?\b/gi, /\bguaranty\b/gi],
|
|
92
|
+
requiresProof: false,
|
|
93
|
+
risk: 'high',
|
|
94
|
+
category: 'Claims',
|
|
95
|
+
alternative: 'designed to help',
|
|
96
|
+
disclaimer: 'Results may vary. See terms for guarantee conditions.',
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
term: '100%',
|
|
100
|
+
patterns: [/\b100\s*%/gi, /\bhundred\s*percent\b/gi],
|
|
101
|
+
requiresProof: false,
|
|
102
|
+
risk: 'high',
|
|
103
|
+
category: 'Claims',
|
|
104
|
+
alternative: 'up to 100%',
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
term: 'always',
|
|
108
|
+
patterns: [/\balways\b/gi],
|
|
109
|
+
requiresProof: false,
|
|
110
|
+
risk: 'medium',
|
|
111
|
+
category: 'Claims',
|
|
112
|
+
alternative: 'consistently',
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
term: 'never',
|
|
116
|
+
patterns: [/\bnever\b/gi],
|
|
117
|
+
requiresProof: false,
|
|
118
|
+
risk: 'medium',
|
|
119
|
+
category: 'Claims',
|
|
120
|
+
alternative: 'designed to prevent',
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
term: 'zero',
|
|
124
|
+
patterns: [/\bzero\s+(?:downtime|errors?|bugs?|issues?|risk)\b/gi],
|
|
125
|
+
requiresProof: false,
|
|
126
|
+
risk: 'medium',
|
|
127
|
+
category: 'Claims',
|
|
128
|
+
alternative: 'minimal',
|
|
129
|
+
},
|
|
130
|
+
{
|
|
131
|
+
term: 'unlimited',
|
|
132
|
+
patterns: [/\bunlimited\b/gi],
|
|
133
|
+
requiresProof: false,
|
|
134
|
+
risk: 'medium',
|
|
135
|
+
category: 'Claims',
|
|
136
|
+
disclaimer: 'Subject to fair use policy',
|
|
137
|
+
},
|
|
138
|
+
|
|
139
|
+
// Superlative Claims
|
|
140
|
+
{
|
|
141
|
+
term: 'best',
|
|
142
|
+
patterns: [/\bbest\b/gi, /\b#1\b/gi, /\bnumber\s*one\b/gi],
|
|
143
|
+
requiresProof: true,
|
|
144
|
+
risk: 'medium',
|
|
145
|
+
category: 'Claims',
|
|
146
|
+
alternative: 'leading',
|
|
147
|
+
},
|
|
148
|
+
{
|
|
149
|
+
term: 'only',
|
|
150
|
+
patterns: [/\bonly\s+(?:solution|platform|provider|option)\b/gi],
|
|
151
|
+
requiresProof: true,
|
|
152
|
+
risk: 'medium',
|
|
153
|
+
category: 'Claims',
|
|
154
|
+
alternative: 'one of few',
|
|
155
|
+
},
|
|
156
|
+
{
|
|
157
|
+
term: 'fastest',
|
|
158
|
+
patterns: [/\bfastest\b/gi],
|
|
159
|
+
requiresProof: true,
|
|
160
|
+
risk: 'medium',
|
|
161
|
+
category: 'Claims',
|
|
162
|
+
alternative: 'fast',
|
|
163
|
+
},
|
|
164
|
+
{
|
|
165
|
+
term: 'most',
|
|
166
|
+
patterns: [/\bmost\s+(?:trusted|secure|reliable|advanced|popular)\b/gi],
|
|
167
|
+
requiresProof: true,
|
|
168
|
+
risk: 'medium',
|
|
169
|
+
category: 'Claims',
|
|
170
|
+
alternative: 'highly',
|
|
171
|
+
},
|
|
172
|
+
|
|
173
|
+
// Financial/ROI Claims
|
|
174
|
+
{
|
|
175
|
+
term: 'ROI',
|
|
176
|
+
patterns: [/\bROI\b/gi, /\breturn\s+on\s+investment\b/gi],
|
|
177
|
+
requiresProof: true,
|
|
178
|
+
risk: 'medium',
|
|
179
|
+
category: 'Financial',
|
|
180
|
+
disclaimer: 'Individual results may vary based on implementation',
|
|
181
|
+
},
|
|
182
|
+
{
|
|
183
|
+
term: 'save money',
|
|
184
|
+
patterns: [/\bsave\s+\$[\d,]+/gi, /\bsave\s+\d+%/gi, /\bcost\s+savings?\b/gi],
|
|
185
|
+
requiresProof: true,
|
|
186
|
+
risk: 'medium',
|
|
187
|
+
category: 'Financial',
|
|
188
|
+
disclaimer: 'Savings based on average customer data',
|
|
189
|
+
},
|
|
190
|
+
|
|
191
|
+
// Industry-Specific
|
|
192
|
+
{
|
|
193
|
+
term: 'medical',
|
|
194
|
+
patterns: [/\bmedical\s*(?:grade|device|approved)\b/gi],
|
|
195
|
+
requiresProof: true,
|
|
196
|
+
risk: 'high',
|
|
197
|
+
category: 'Industry',
|
|
198
|
+
disclaimer: 'For informational purposes only. Not a medical device.',
|
|
199
|
+
},
|
|
200
|
+
{
|
|
201
|
+
term: 'FDA',
|
|
202
|
+
patterns: [/\bFDA\s*(?:approved|cleared|registered)\b/gi],
|
|
203
|
+
requiresProof: true,
|
|
204
|
+
risk: 'high',
|
|
205
|
+
category: 'Industry',
|
|
206
|
+
},
|
|
207
|
+
{
|
|
208
|
+
term: 'bank-level',
|
|
209
|
+
patterns: [/\bbank[-\s]*level\b/gi, /\bbanking[-\s]*grade\b/gi],
|
|
210
|
+
requiresProof: false,
|
|
211
|
+
risk: 'low',
|
|
212
|
+
category: 'Security',
|
|
213
|
+
disclaimer: 'Refers to AES-256 encryption standard',
|
|
214
|
+
},
|
|
215
|
+
];
|
|
216
|
+
|
|
217
|
+
// ============================================
|
|
218
|
+
// Required Disclaimers by Context
|
|
219
|
+
// ============================================
|
|
220
|
+
|
|
221
|
+
const CONTEXT_DISCLAIMERS: Record<string, { patterns: RegExp[]; disclaimer: string }> = {
|
|
222
|
+
testimonials: {
|
|
223
|
+
patterns: [/testimonial/gi, /customer\s+(?:story|review|quote)/gi],
|
|
224
|
+
disclaimer: 'Individual results may vary',
|
|
225
|
+
},
|
|
226
|
+
pricing: {
|
|
227
|
+
patterns: [/pricing/gi, /\$/gi, /cost/gi, /fee/gi],
|
|
228
|
+
disclaimer: 'Pricing subject to change. Additional fees may apply.',
|
|
229
|
+
},
|
|
230
|
+
trials: {
|
|
231
|
+
patterns: [/free\s+trial/gi, /try\s+free/gi, /\d+[-\s]*day\s+trial/gi],
|
|
232
|
+
disclaimer: 'Credit card may be required. Cancel anytime.',
|
|
233
|
+
},
|
|
234
|
+
results: {
|
|
235
|
+
patterns: [/result[s]?/gi, /outcome[s]?/gi, /case\s+study/gi],
|
|
236
|
+
disclaimer: 'Results shown are representative examples. Individual results may vary.',
|
|
237
|
+
},
|
|
238
|
+
};
|
|
239
|
+
|
|
240
|
+
// ============================================
|
|
241
|
+
// Scoring Weights
|
|
242
|
+
// ============================================
|
|
243
|
+
|
|
244
|
+
const WEIGHTS = {
|
|
245
|
+
regulatedTerms: 0.40,
|
|
246
|
+
disclaimers: 0.30,
|
|
247
|
+
substantiation: 0.30,
|
|
248
|
+
};
|
|
249
|
+
|
|
250
|
+
// ============================================
|
|
251
|
+
// Main Analysis Function
|
|
252
|
+
// ============================================
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Analyze content for regulatory compliance issues
|
|
256
|
+
*/
|
|
257
|
+
export function analyzeCompliance(content: AnalysisInput): ComplianceAnalysis {
|
|
258
|
+
const findings: Finding[] = [];
|
|
259
|
+
const recommendations: Recommendation[] = [];
|
|
260
|
+
const regulatedTermsFound: RegulatedTerm[] = [];
|
|
261
|
+
const missingDisclaimers: string[] = [];
|
|
262
|
+
const unsubstantiatedClaims: string[] = [];
|
|
263
|
+
|
|
264
|
+
// Check regulated terms
|
|
265
|
+
const termsResult = checkRegulatedTerms(
|
|
266
|
+
content,
|
|
267
|
+
findings,
|
|
268
|
+
recommendations,
|
|
269
|
+
regulatedTermsFound
|
|
270
|
+
);
|
|
271
|
+
|
|
272
|
+
// Check disclaimers
|
|
273
|
+
const disclaimerResult = checkDisclaimers(
|
|
274
|
+
content,
|
|
275
|
+
findings,
|
|
276
|
+
recommendations,
|
|
277
|
+
missingDisclaimers
|
|
278
|
+
);
|
|
279
|
+
|
|
280
|
+
// Check claim substantiation
|
|
281
|
+
const substantiationResult = checkSubstantiation(
|
|
282
|
+
content,
|
|
283
|
+
findings,
|
|
284
|
+
recommendations,
|
|
285
|
+
unsubstantiatedClaims
|
|
286
|
+
);
|
|
287
|
+
|
|
288
|
+
// Calculate overall score
|
|
289
|
+
const overallScore = Math.round(
|
|
290
|
+
termsResult.score * WEIGHTS.regulatedTerms +
|
|
291
|
+
disclaimerResult.score * WEIGHTS.disclaimers +
|
|
292
|
+
substantiationResult.score * WEIGHTS.substantiation
|
|
293
|
+
);
|
|
294
|
+
|
|
295
|
+
return {
|
|
296
|
+
overallScore,
|
|
297
|
+
regulatedTermsFound,
|
|
298
|
+
missingDisclaimers,
|
|
299
|
+
unsubstantiatedClaims,
|
|
300
|
+
findings,
|
|
301
|
+
recommendations,
|
|
302
|
+
};
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
// ============================================
|
|
306
|
+
// Regulated Terms Check
|
|
307
|
+
// ============================================
|
|
308
|
+
|
|
309
|
+
interface TermsResult {
|
|
310
|
+
score: number;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
function checkRegulatedTerms(
|
|
314
|
+
content: AnalysisInput,
|
|
315
|
+
findings: Finding[],
|
|
316
|
+
recommendations: Recommendation[],
|
|
317
|
+
regulatedTermsFound: RegulatedTerm[]
|
|
318
|
+
): TermsResult {
|
|
319
|
+
let score = 100;
|
|
320
|
+
const allText = getAllText(content);
|
|
321
|
+
|
|
322
|
+
for (const termDef of REGULATED_TERMS) {
|
|
323
|
+
for (const pattern of termDef.patterns) {
|
|
324
|
+
const matches = allText.matchAll(pattern);
|
|
325
|
+
const matchArray = [...matches];
|
|
326
|
+
|
|
327
|
+
if (matchArray.length > 0) {
|
|
328
|
+
// Find location of match
|
|
329
|
+
const location = findTermLocation(content, pattern);
|
|
330
|
+
|
|
331
|
+
const regulatedTerm: RegulatedTerm = {
|
|
332
|
+
term: termDef.term,
|
|
333
|
+
location,
|
|
334
|
+
requiresProof: termDef.requiresProof,
|
|
335
|
+
hasProof: false, // Would need external verification
|
|
336
|
+
disclaimer: termDef.disclaimer,
|
|
337
|
+
risk: termDef.risk,
|
|
338
|
+
};
|
|
339
|
+
|
|
340
|
+
regulatedTermsFound.push(regulatedTerm);
|
|
341
|
+
|
|
342
|
+
// Score based on risk level
|
|
343
|
+
const riskPenalty = termDef.risk === 'high' ? 15 : termDef.risk === 'medium' ? 8 : 3;
|
|
344
|
+
|
|
345
|
+
if (termDef.requiresProof) {
|
|
346
|
+
findings.push({
|
|
347
|
+
severity: termDef.risk === 'high' ? 'warning' : 'info',
|
|
348
|
+
category: `Compliance - ${termDef.category}`,
|
|
349
|
+
message: `Regulated term "${termDef.term}" requires substantiation`,
|
|
350
|
+
location,
|
|
351
|
+
evidence: matchArray[0][0],
|
|
352
|
+
});
|
|
353
|
+
score -= riskPenalty;
|
|
354
|
+
|
|
355
|
+
if (termDef.disclaimer) {
|
|
356
|
+
recommendations.push({
|
|
357
|
+
priority: termDef.risk === 'high' ? 'high' : 'medium',
|
|
358
|
+
category: 'Compliance',
|
|
359
|
+
current: `Uses "${termDef.term}" without disclaimer`,
|
|
360
|
+
suggested: `Add disclaimer: "${termDef.disclaimer}"`,
|
|
361
|
+
rationale: 'Regulated claims require proper disclaimers',
|
|
362
|
+
});
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
if (termDef.alternative) {
|
|
367
|
+
recommendations.push({
|
|
368
|
+
priority: termDef.risk === 'high' ? 'high' : 'medium',
|
|
369
|
+
category: 'Compliance',
|
|
370
|
+
current: `Uses "${termDef.term}"`,
|
|
371
|
+
suggested: `Consider using "${termDef.alternative}" instead`,
|
|
372
|
+
rationale: 'Reduces legal risk while maintaining message',
|
|
373
|
+
});
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
break; // Only count each term once
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
// Report summary
|
|
382
|
+
const highRiskCount = regulatedTermsFound.filter(t => t.risk === 'high').length;
|
|
383
|
+
const mediumRiskCount = regulatedTermsFound.filter(t => t.risk === 'medium').length;
|
|
384
|
+
|
|
385
|
+
if (highRiskCount > 0) {
|
|
386
|
+
findings.push({
|
|
387
|
+
severity: 'warning',
|
|
388
|
+
category: 'Compliance Summary',
|
|
389
|
+
message: `Found ${highRiskCount} high-risk regulated term(s)`,
|
|
390
|
+
});
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
if (mediumRiskCount > 0) {
|
|
394
|
+
findings.push({
|
|
395
|
+
severity: 'info',
|
|
396
|
+
category: 'Compliance Summary',
|
|
397
|
+
message: `Found ${mediumRiskCount} medium-risk regulated term(s)`,
|
|
398
|
+
});
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
if (regulatedTermsFound.length === 0) {
|
|
402
|
+
findings.push({
|
|
403
|
+
severity: 'pass',
|
|
404
|
+
category: 'Compliance Summary',
|
|
405
|
+
message: 'No high-risk regulated terms found',
|
|
406
|
+
});
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
return { score: Math.max(0, score) };
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
// ============================================
|
|
413
|
+
// Disclaimer Check
|
|
414
|
+
// ============================================
|
|
415
|
+
|
|
416
|
+
interface DisclaimerResult {
|
|
417
|
+
score: number;
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
function checkDisclaimers(
|
|
421
|
+
content: AnalysisInput,
|
|
422
|
+
findings: Finding[],
|
|
423
|
+
recommendations: Recommendation[],
|
|
424
|
+
missingDisclaimers: string[]
|
|
425
|
+
): DisclaimerResult {
|
|
426
|
+
let score = 100;
|
|
427
|
+
const allText = getAllText(content).toLowerCase();
|
|
428
|
+
|
|
429
|
+
// Check for context-specific disclaimers
|
|
430
|
+
for (const [context, config] of Object.entries(CONTEXT_DISCLAIMERS)) {
|
|
431
|
+
const hasContext = config.patterns.some(pattern => pattern.test(allText));
|
|
432
|
+
|
|
433
|
+
if (hasContext) {
|
|
434
|
+
// Check if corresponding disclaimer exists
|
|
435
|
+
const hasDisclaimer = allText.includes('results may vary') ||
|
|
436
|
+
allText.includes('individual results') ||
|
|
437
|
+
allText.includes('subject to') ||
|
|
438
|
+
allText.includes('terms apply') ||
|
|
439
|
+
allText.includes('disclaimer');
|
|
440
|
+
|
|
441
|
+
if (!hasDisclaimer) {
|
|
442
|
+
missingDisclaimers.push(config.disclaimer);
|
|
443
|
+
findings.push({
|
|
444
|
+
severity: 'info',
|
|
445
|
+
category: 'Compliance - Disclaimers',
|
|
446
|
+
message: `${context.charAt(0).toUpperCase() + context.slice(1)} content may need disclaimer`,
|
|
447
|
+
});
|
|
448
|
+
score -= 10;
|
|
449
|
+
recommendations.push({
|
|
450
|
+
priority: 'medium',
|
|
451
|
+
category: 'Disclaimers',
|
|
452
|
+
current: `${context} content without disclaimer`,
|
|
453
|
+
suggested: `Add: "${config.disclaimer}"`,
|
|
454
|
+
rationale: 'Disclaimers protect against legal claims',
|
|
455
|
+
});
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
// Check for financial projections
|
|
461
|
+
const financialPatterns = [
|
|
462
|
+
/\bwill\s+save\b/gi,
|
|
463
|
+
/\bwill\s+(?:increase|boost|grow)\b/gi,
|
|
464
|
+
/\bexpect\s+to\b/gi,
|
|
465
|
+
];
|
|
466
|
+
|
|
467
|
+
const hasFinancialProjections = financialPatterns.some(pattern => pattern.test(allText));
|
|
468
|
+
if (hasFinancialProjections) {
|
|
469
|
+
const hasForwardDisclaimer = allText.includes('forward-looking') ||
|
|
470
|
+
allText.includes('no guarantee') ||
|
|
471
|
+
allText.includes('may vary');
|
|
472
|
+
|
|
473
|
+
if (!hasForwardDisclaimer) {
|
|
474
|
+
missingDisclaimers.push('Projections are estimates and actual results may vary');
|
|
475
|
+
findings.push({
|
|
476
|
+
severity: 'warning',
|
|
477
|
+
category: 'Compliance - Disclaimers',
|
|
478
|
+
message: 'Financial projections may need disclaimer',
|
|
479
|
+
});
|
|
480
|
+
score -= 15;
|
|
481
|
+
recommendations.push({
|
|
482
|
+
priority: 'high',
|
|
483
|
+
category: 'Disclaimers',
|
|
484
|
+
current: 'Financial projections without disclaimer',
|
|
485
|
+
suggested: 'Add: "Projections are estimates and actual results may vary"',
|
|
486
|
+
rationale: 'Forward-looking statements require appropriate disclaimers',
|
|
487
|
+
});
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
// Report findings
|
|
492
|
+
if (missingDisclaimers.length === 0) {
|
|
493
|
+
findings.push({
|
|
494
|
+
severity: 'pass',
|
|
495
|
+
category: 'Compliance - Disclaimers',
|
|
496
|
+
message: 'No obvious missing disclaimers detected',
|
|
497
|
+
});
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
return { score: Math.max(0, score) };
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
// ============================================
|
|
504
|
+
// Claim Substantiation Check
|
|
505
|
+
// ============================================
|
|
506
|
+
|
|
507
|
+
interface SubstantiationResult {
|
|
508
|
+
score: number;
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
function checkSubstantiation(
|
|
512
|
+
content: AnalysisInput,
|
|
513
|
+
findings: Finding[],
|
|
514
|
+
recommendations: Recommendation[],
|
|
515
|
+
unsubstantiatedClaims: string[]
|
|
516
|
+
): SubstantiationResult {
|
|
517
|
+
let score = 100;
|
|
518
|
+
|
|
519
|
+
// Check claims for substantiation
|
|
520
|
+
for (const claim of content.claims) {
|
|
521
|
+
const claimLower = claim.toLowerCase();
|
|
522
|
+
|
|
523
|
+
// Check if claim has supporting evidence patterns
|
|
524
|
+
const hasEvidence =
|
|
525
|
+
/\bstudy\b/i.test(claim) ||
|
|
526
|
+
/\bresearch\b/i.test(claim) ||
|
|
527
|
+
/\bdata\b/i.test(claim) ||
|
|
528
|
+
/\bsurvey\b/i.test(claim) ||
|
|
529
|
+
/\breport\b/i.test(claim) ||
|
|
530
|
+
/according to/i.test(claim) ||
|
|
531
|
+
/based on/i.test(claim);
|
|
532
|
+
|
|
533
|
+
// Check if it's a risky claim type
|
|
534
|
+
const isRiskyClaim =
|
|
535
|
+
/\b(always|never|guaranteed|100%|best|only|most|fastest)\b/i.test(claimLower);
|
|
536
|
+
|
|
537
|
+
if (isRiskyClaim && !hasEvidence) {
|
|
538
|
+
unsubstantiatedClaims.push(claim);
|
|
539
|
+
findings.push({
|
|
540
|
+
severity: 'warning',
|
|
541
|
+
category: 'Compliance - Substantiation',
|
|
542
|
+
message: 'Claim may need substantiation',
|
|
543
|
+
evidence: truncate(claim, 100),
|
|
544
|
+
});
|
|
545
|
+
score -= 10;
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
// Check stats for sources
|
|
550
|
+
const statsWithoutSources = content.stats.filter(stat => {
|
|
551
|
+
const labelLower = stat.label.toLowerCase();
|
|
552
|
+
return !labelLower.includes('source') &&
|
|
553
|
+
!labelLower.includes('based on') &&
|
|
554
|
+
!labelLower.includes('according') &&
|
|
555
|
+
!labelLower.includes('study') &&
|
|
556
|
+
!labelLower.includes('report');
|
|
557
|
+
});
|
|
558
|
+
|
|
559
|
+
if (statsWithoutSources.length > 0 && content.stats.length > 0) {
|
|
560
|
+
const percentage = Math.round((statsWithoutSources.length / content.stats.length) * 100);
|
|
561
|
+
if (percentage > 50) {
|
|
562
|
+
findings.push({
|
|
563
|
+
severity: 'info',
|
|
564
|
+
category: 'Compliance - Substantiation',
|
|
565
|
+
message: `${percentage}% of statistics lack source attribution`,
|
|
566
|
+
});
|
|
567
|
+
score -= 5;
|
|
568
|
+
recommendations.push({
|
|
569
|
+
priority: 'low',
|
|
570
|
+
category: 'Substantiation',
|
|
571
|
+
current: 'Statistics without sources',
|
|
572
|
+
suggested: 'Add source attribution for third-party statistics',
|
|
573
|
+
rationale: 'Sourced statistics are more defensible',
|
|
574
|
+
});
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
// Report findings
|
|
579
|
+
if (unsubstantiatedClaims.length === 0) {
|
|
580
|
+
findings.push({
|
|
581
|
+
severity: 'pass',
|
|
582
|
+
category: 'Compliance - Substantiation',
|
|
583
|
+
message: 'No obviously unsubstantiated claims detected',
|
|
584
|
+
});
|
|
585
|
+
} else {
|
|
586
|
+
recommendations.push({
|
|
587
|
+
priority: 'high',
|
|
588
|
+
category: 'Substantiation',
|
|
589
|
+
current: `${unsubstantiatedClaims.length} claim(s) lack substantiation`,
|
|
590
|
+
suggested: 'Add supporting data, sources, or soften language',
|
|
591
|
+
rationale: 'Unsubstantiated claims can lead to legal challenges',
|
|
592
|
+
});
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
return { score: Math.max(0, score) };
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
// ============================================
|
|
599
|
+
// Helper Functions
|
|
600
|
+
// ============================================
|
|
601
|
+
|
|
602
|
+
/**
|
|
603
|
+
* Get all text from content
|
|
604
|
+
*/
|
|
605
|
+
function getAllText(content: AnalysisInput): string {
|
|
606
|
+
return [
|
|
607
|
+
content.title,
|
|
608
|
+
...content.headings,
|
|
609
|
+
...content.paragraphs,
|
|
610
|
+
...content.ctas.map(c => c.text),
|
|
611
|
+
...content.claims,
|
|
612
|
+
].join(' ');
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
/**
|
|
616
|
+
* Find the location of a term in content
|
|
617
|
+
*/
|
|
618
|
+
function findTermLocation(content: AnalysisInput, pattern: RegExp): string {
|
|
619
|
+
// Check title
|
|
620
|
+
if (pattern.test(content.title)) {
|
|
621
|
+
return 'Page title';
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
// Check headings
|
|
625
|
+
for (let i = 0; i < content.headings.length; i++) {
|
|
626
|
+
if (pattern.test(content.headings[i])) {
|
|
627
|
+
return `Heading: ${truncate(content.headings[i], 50)}`;
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
// Check sections
|
|
632
|
+
for (const section of content.sections) {
|
|
633
|
+
const sectionText = section.content.join(' ');
|
|
634
|
+
if (pattern.test(sectionText)) {
|
|
635
|
+
return `Section: ${section.type} (${section.heading || section.id})`;
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
// Check CTAs
|
|
640
|
+
for (const cta of content.ctas) {
|
|
641
|
+
if (pattern.test(cta.text)) {
|
|
642
|
+
return `CTA: ${cta.text}`;
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
return 'Body content';
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
/**
|
|
650
|
+
* Truncate string with ellipsis
|
|
651
|
+
*/
|
|
652
|
+
function truncate(str: string, maxLength: number): string {
|
|
653
|
+
if (str.length <= maxLength) return str;
|
|
654
|
+
return str.slice(0, maxLength - 3) + '...';
|
|
655
|
+
}
|