popeye-cli 2.1.0 → 2.7.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/adapters/gemini.d.ts +14 -0
- package/dist/adapters/gemini.d.ts.map +1 -1
- package/dist/adapters/gemini.js +41 -6
- package/dist/adapters/gemini.js.map +1 -1
- package/dist/adapters/grok.d.ts +14 -0
- package/dist/adapters/grok.d.ts.map +1 -1
- package/dist/adapters/grok.js +42 -6
- package/dist/adapters/grok.js.map +1 -1
- package/dist/adapters/openai.d.ts +10 -0
- package/dist/adapters/openai.d.ts.map +1 -1
- package/dist/adapters/openai.js +44 -5
- package/dist/adapters/openai.js.map +1 -1
- package/dist/cli/commands/create.js +1 -1
- package/dist/cli/commands/create.js.map +1 -1
- package/dist/cli/interactive.d.ts.map +1 -1
- package/dist/cli/interactive.js +328 -21
- package/dist/cli/interactive.js.map +1 -1
- package/dist/generators/all.d.ts.map +1 -1
- package/dist/generators/all.js +25 -2
- package/dist/generators/all.js.map +1 -1
- package/dist/generators/doc-parser.d.ts +21 -6
- package/dist/generators/doc-parser.d.ts.map +1 -1
- package/dist/generators/doc-parser.js +55 -4
- package/dist/generators/doc-parser.js.map +1 -1
- package/dist/generators/templates/fullstack.js +1 -1
- package/dist/generators/templates/website-components.js +1 -1
- package/dist/generators/templates/website-components.js.map +1 -1
- package/dist/generators/templates/website-config.d.ts +4 -1
- package/dist/generators/templates/website-config.d.ts.map +1 -1
- package/dist/generators/templates/website-config.js +17 -11
- package/dist/generators/templates/website-config.js.map +1 -1
- package/dist/generators/templates/website-conversion.js +1 -1
- package/dist/generators/templates/website-conversion.js.map +1 -1
- package/dist/generators/templates/website-landing.js +1 -1
- package/dist/generators/templates/website-landing.js.map +1 -1
- package/dist/generators/templates/website-layout.d.ts +36 -4
- package/dist/generators/templates/website-layout.d.ts.map +1 -1
- package/dist/generators/templates/website-layout.js +466 -23
- package/dist/generators/templates/website-layout.js.map +1 -1
- package/dist/generators/templates/website-pricing.js +1 -1
- package/dist/generators/templates/website-pricing.js.map +1 -1
- package/dist/generators/templates/website-sections.js +1 -1
- package/dist/generators/templates/website-sections.js.map +1 -1
- package/dist/generators/templates/website-seo.d.ts.map +1 -1
- package/dist/generators/templates/website-seo.js +4 -1
- package/dist/generators/templates/website-seo.js.map +1 -1
- package/dist/generators/templates/website.d.ts +1 -1
- package/dist/generators/templates/website.d.ts.map +1 -1
- package/dist/generators/templates/website.js +1 -1
- package/dist/generators/templates/website.js.map +1 -1
- package/dist/generators/website-content-ai.d.ts +52 -0
- package/dist/generators/website-content-ai.d.ts.map +1 -0
- package/dist/generators/website-content-ai.js +141 -0
- package/dist/generators/website-content-ai.js.map +1 -0
- package/dist/generators/website-content-scanner.d.ts +1 -1
- package/dist/generators/website-content-scanner.d.ts.map +1 -1
- package/dist/generators/website-content-scanner.js +98 -1
- package/dist/generators/website-content-scanner.js.map +1 -1
- package/dist/generators/website-context.d.ts +34 -1
- package/dist/generators/website-context.d.ts.map +1 -1
- package/dist/generators/website-context.js +131 -9
- package/dist/generators/website-context.js.map +1 -1
- package/dist/generators/website-debug.d.ts +12 -0
- package/dist/generators/website-debug.d.ts.map +1 -1
- package/dist/generators/website-debug.js +16 -0
- package/dist/generators/website-debug.js.map +1 -1
- package/dist/generators/website.d.ts.map +1 -1
- package/dist/generators/website.js +26 -4
- package/dist/generators/website.js.map +1 -1
- package/dist/pipeline/artifact-manager.d.ts.map +1 -1
- package/dist/pipeline/artifact-manager.js +3 -0
- package/dist/pipeline/artifact-manager.js.map +1 -1
- package/dist/pipeline/auto-recovery.d.ts +56 -0
- package/dist/pipeline/auto-recovery.d.ts.map +1 -0
- package/dist/pipeline/auto-recovery.js +185 -0
- package/dist/pipeline/auto-recovery.js.map +1 -0
- package/dist/pipeline/change-request.d.ts +39 -0
- package/dist/pipeline/change-request.d.ts.map +1 -1
- package/dist/pipeline/change-request.js +40 -1
- package/dist/pipeline/change-request.js.map +1 -1
- package/dist/pipeline/check-runner.d.ts +30 -1
- package/dist/pipeline/check-runner.d.ts.map +1 -1
- package/dist/pipeline/check-runner.js +122 -1
- package/dist/pipeline/check-runner.js.map +1 -1
- package/dist/pipeline/command-resolver.d.ts.map +1 -1
- package/dist/pipeline/command-resolver.js +33 -2
- package/dist/pipeline/command-resolver.js.map +1 -1
- package/dist/pipeline/consensus/arbitrator-query.d.ts +22 -0
- package/dist/pipeline/consensus/arbitrator-query.d.ts.map +1 -0
- package/dist/pipeline/consensus/arbitrator-query.js +70 -0
- package/dist/pipeline/consensus/arbitrator-query.js.map +1 -0
- package/dist/pipeline/consensus/consensus-runner.d.ts +131 -7
- package/dist/pipeline/consensus/consensus-runner.d.ts.map +1 -1
- package/dist/pipeline/consensus/consensus-runner.js +809 -35
- package/dist/pipeline/consensus/consensus-runner.js.map +1 -1
- package/dist/pipeline/cr-lifecycle.d.ts +42 -0
- package/dist/pipeline/cr-lifecycle.d.ts.map +1 -0
- package/dist/pipeline/cr-lifecycle.js +89 -0
- package/dist/pipeline/cr-lifecycle.js.map +1 -0
- package/dist/pipeline/gate-engine.d.ts +1 -0
- package/dist/pipeline/gate-engine.d.ts.map +1 -1
- package/dist/pipeline/gate-engine.js +27 -8
- package/dist/pipeline/gate-engine.js.map +1 -1
- package/dist/pipeline/migration.d.ts.map +1 -1
- package/dist/pipeline/migration.js +3 -26
- package/dist/pipeline/migration.js.map +1 -1
- package/dist/pipeline/orchestrator.d.ts +1 -1
- package/dist/pipeline/orchestrator.d.ts.map +1 -1
- package/dist/pipeline/orchestrator.js +311 -16
- package/dist/pipeline/orchestrator.js.map +1 -1
- package/dist/pipeline/packets/consensus-packet-builder.d.ts +15 -4
- package/dist/pipeline/packets/consensus-packet-builder.d.ts.map +1 -1
- package/dist/pipeline/packets/consensus-packet-builder.js +29 -17
- package/dist/pipeline/packets/consensus-packet-builder.js.map +1 -1
- package/dist/pipeline/phases/architecture.d.ts.map +1 -1
- package/dist/pipeline/phases/architecture.js +5 -3
- package/dist/pipeline/phases/architecture.js.map +1 -1
- package/dist/pipeline/phases/audit.d.ts.map +1 -1
- package/dist/pipeline/phases/audit.js +5 -3
- package/dist/pipeline/phases/audit.js.map +1 -1
- package/dist/pipeline/phases/consensus-architecture.d.ts.map +1 -1
- package/dist/pipeline/phases/consensus-architecture.js +10 -1
- package/dist/pipeline/phases/consensus-architecture.js.map +1 -1
- package/dist/pipeline/phases/consensus-master-plan.d.ts.map +1 -1
- package/dist/pipeline/phases/consensus-master-plan.js +10 -3
- package/dist/pipeline/phases/consensus-master-plan.js.map +1 -1
- package/dist/pipeline/phases/consensus-role-plans.d.ts.map +1 -1
- package/dist/pipeline/phases/consensus-role-plans.js +10 -1
- package/dist/pipeline/phases/consensus-role-plans.js.map +1 -1
- package/dist/pipeline/phases/done.d.ts.map +1 -1
- package/dist/pipeline/phases/done.js +9 -4
- package/dist/pipeline/phases/done.js.map +1 -1
- package/dist/pipeline/phases/intake.d.ts +1 -0
- package/dist/pipeline/phases/intake.d.ts.map +1 -1
- package/dist/pipeline/phases/intake.js +56 -13
- package/dist/pipeline/phases/intake.js.map +1 -1
- package/dist/pipeline/phases/phase-context.d.ts +2 -0
- package/dist/pipeline/phases/phase-context.d.ts.map +1 -1
- package/dist/pipeline/phases/phase-context.js +3 -1
- package/dist/pipeline/phases/phase-context.js.map +1 -1
- package/dist/pipeline/phases/production-gate.d.ts.map +1 -1
- package/dist/pipeline/phases/production-gate.js +28 -3
- package/dist/pipeline/phases/production-gate.js.map +1 -1
- package/dist/pipeline/phases/qa-validation.d.ts.map +1 -1
- package/dist/pipeline/phases/qa-validation.js +38 -5
- package/dist/pipeline/phases/qa-validation.js.map +1 -1
- package/dist/pipeline/phases/recovery-loop.d.ts +2 -0
- package/dist/pipeline/phases/recovery-loop.d.ts.map +1 -1
- package/dist/pipeline/phases/recovery-loop.js +200 -6
- package/dist/pipeline/phases/recovery-loop.js.map +1 -1
- package/dist/pipeline/phases/review.d.ts.map +1 -1
- package/dist/pipeline/phases/review.js +58 -28
- package/dist/pipeline/phases/review.js.map +1 -1
- package/dist/pipeline/phases/role-planning.d.ts.map +1 -1
- package/dist/pipeline/phases/role-planning.js +20 -5
- package/dist/pipeline/phases/role-planning.js.map +1 -1
- package/dist/pipeline/phases/stuck.d.ts.map +1 -1
- package/dist/pipeline/phases/stuck.js +10 -0
- package/dist/pipeline/phases/stuck.js.map +1 -1
- package/dist/pipeline/repo-snapshot.d.ts.map +1 -1
- package/dist/pipeline/repo-snapshot.js +3 -0
- package/dist/pipeline/repo-snapshot.js.map +1 -1
- package/dist/pipeline/role-execution-adapter.d.ts +2 -1
- package/dist/pipeline/role-execution-adapter.d.ts.map +1 -1
- package/dist/pipeline/role-execution-adapter.js +22 -7
- package/dist/pipeline/role-execution-adapter.js.map +1 -1
- package/dist/pipeline/skill-loader.d.ts +19 -0
- package/dist/pipeline/skill-loader.d.ts.map +1 -1
- package/dist/pipeline/skill-loader.js +22 -0
- package/dist/pipeline/skill-loader.js.map +1 -1
- package/dist/pipeline/skills/constitution-generator.d.ts +51 -0
- package/dist/pipeline/skills/constitution-generator.d.ts.map +1 -0
- package/dist/pipeline/skills/constitution-generator.js +210 -0
- package/dist/pipeline/skills/constitution-generator.js.map +1 -0
- package/dist/pipeline/skills/coverage-gate.d.ts +44 -0
- package/dist/pipeline/skills/coverage-gate.d.ts.map +1 -0
- package/dist/pipeline/skills/coverage-gate.js +143 -0
- package/dist/pipeline/skills/coverage-gate.js.map +1 -0
- package/dist/pipeline/skills/generator.d.ts +65 -0
- package/dist/pipeline/skills/generator.d.ts.map +1 -0
- package/dist/pipeline/skills/generator.js +221 -0
- package/dist/pipeline/skills/generator.js.map +1 -0
- package/dist/pipeline/skills/role-map.d.ts +38 -0
- package/dist/pipeline/skills/role-map.d.ts.map +1 -0
- package/dist/pipeline/skills/role-map.js +234 -0
- package/dist/pipeline/skills/role-map.js.map +1 -0
- package/dist/pipeline/skills/types.d.ts +47 -0
- package/dist/pipeline/skills/types.d.ts.map +1 -0
- package/dist/pipeline/skills/types.js +5 -0
- package/dist/pipeline/skills/types.js.map +1 -0
- package/dist/pipeline/skills/usage-registry.d.ts +48 -0
- package/dist/pipeline/skills/usage-registry.d.ts.map +1 -0
- package/dist/pipeline/skills/usage-registry.js +55 -0
- package/dist/pipeline/skills/usage-registry.js.map +1 -0
- package/dist/pipeline/strategy-context.d.ts +20 -0
- package/dist/pipeline/strategy-context.d.ts.map +1 -0
- package/dist/pipeline/strategy-context.js +55 -0
- package/dist/pipeline/strategy-context.js.map +1 -0
- package/dist/pipeline/type-defs/artifacts.d.ts +30 -5
- package/dist/pipeline/type-defs/artifacts.d.ts.map +1 -1
- package/dist/pipeline/type-defs/artifacts.js +5 -0
- package/dist/pipeline/type-defs/artifacts.js.map +1 -1
- package/dist/pipeline/type-defs/audit.d.ts +28 -13
- package/dist/pipeline/type-defs/audit.d.ts.map +1 -1
- package/dist/pipeline/type-defs/checks.d.ts +19 -8
- package/dist/pipeline/type-defs/checks.d.ts.map +1 -1
- package/dist/pipeline/type-defs/checks.js +4 -0
- package/dist/pipeline/type-defs/checks.js.map +1 -1
- package/dist/pipeline/type-defs/packets.d.ts +119 -18
- package/dist/pipeline/type-defs/packets.d.ts.map +1 -1
- package/dist/pipeline/type-defs/packets.js +17 -1
- package/dist/pipeline/type-defs/packets.js.map +1 -1
- package/dist/pipeline/type-defs/state.d.ts +165 -16
- package/dist/pipeline/type-defs/state.d.ts.map +1 -1
- package/dist/pipeline/type-defs/state.js +26 -1
- package/dist/pipeline/type-defs/state.js.map +1 -1
- package/dist/shared/text-utils.d.ts +23 -0
- package/dist/shared/text-utils.d.ts.map +1 -0
- package/dist/shared/text-utils.js +66 -0
- package/dist/shared/text-utils.js.map +1 -0
- package/dist/shared/website-strategy-format.d.ts +18 -0
- package/dist/shared/website-strategy-format.d.ts.map +1 -0
- package/dist/shared/website-strategy-format.js +47 -0
- package/dist/shared/website-strategy-format.js.map +1 -0
- package/dist/state/index.d.ts +2 -0
- package/dist/state/index.d.ts.map +1 -1
- package/dist/state/index.js +57 -8
- package/dist/state/index.js.map +1 -1
- package/dist/types/consensus.d.ts +1 -0
- package/dist/types/consensus.d.ts.map +1 -1
- package/dist/types/consensus.js.map +1 -1
- package/dist/types/website-strategy.d.ts +1 -1
- package/dist/types/workflow.d.ts +447 -0
- package/dist/types/workflow.d.ts.map +1 -1
- package/dist/types/workflow.js +3 -0
- package/dist/types/workflow.js.map +1 -1
- package/dist/upgrade/handlers.d.ts.map +1 -1
- package/dist/upgrade/handlers.js +6 -3
- package/dist/upgrade/handlers.js.map +1 -1
- package/dist/workflow/consensus.d.ts.map +1 -1
- package/dist/workflow/consensus.js +1 -0
- package/dist/workflow/consensus.js.map +1 -1
- package/dist/workflow/website-strategy.d.ts.map +1 -1
- package/dist/workflow/website-strategy.js +2 -29
- package/dist/workflow/website-strategy.js.map +1 -1
- package/dist/workflow/website-updater.d.ts.map +1 -1
- package/dist/workflow/website-updater.js +3 -2
- package/dist/workflow/website-updater.js.map +1 -1
- package/package.json +1 -1
- package/src/adapters/gemini.ts +51 -6
- package/src/adapters/grok.ts +51 -6
- package/src/adapters/openai.ts +53 -5
- package/src/cli/commands/create.ts +1 -1
- package/src/cli/interactive.ts +337 -20
- package/src/generators/all.ts +25 -2
- package/src/generators/doc-parser.ts +75 -15
- package/src/generators/templates/fullstack.ts +1 -1
- package/src/generators/templates/website-components.ts +1 -1
- package/src/generators/templates/website-config.ts +23 -11
- package/src/generators/templates/website-conversion.ts +1 -1
- package/src/generators/templates/website-landing.ts +1 -1
- package/src/generators/templates/website-layout.ts +491 -23
- package/src/generators/templates/website-pricing.ts +1 -1
- package/src/generators/templates/website-sections.ts +1 -1
- package/src/generators/templates/website-seo.ts +4 -1
- package/src/generators/templates/website.ts +3 -0
- package/src/generators/website-content-ai.ts +186 -0
- package/src/generators/website-content-scanner.ts +113 -1
- package/src/generators/website-context.ts +151 -12
- package/src/generators/website-debug.ts +26 -0
- package/src/generators/website.ts +28 -3
- package/src/pipeline/artifact-manager.ts +3 -0
- package/src/pipeline/auto-recovery.ts +283 -0
- package/src/pipeline/change-request.ts +63 -1
- package/src/pipeline/check-runner.ts +141 -2
- package/src/pipeline/command-resolver.ts +34 -2
- package/src/pipeline/consensus/arbitrator-query.ts +101 -0
- package/src/pipeline/consensus/consensus-runner.ts +1099 -42
- package/src/pipeline/cr-lifecycle.ts +103 -0
- package/src/pipeline/gate-engine.ts +36 -8
- package/src/pipeline/migration.ts +5 -30
- package/src/pipeline/orchestrator.ts +367 -16
- package/src/pipeline/packets/consensus-packet-builder.ts +44 -18
- package/src/pipeline/phases/architecture.ts +6 -3
- package/src/pipeline/phases/audit.ts +6 -3
- package/src/pipeline/phases/consensus-architecture.ts +10 -1
- package/src/pipeline/phases/consensus-master-plan.ts +10 -3
- package/src/pipeline/phases/consensus-role-plans.ts +10 -1
- package/src/pipeline/phases/done.ts +15 -4
- package/src/pipeline/phases/intake.ts +67 -14
- package/src/pipeline/phases/phase-context.ts +6 -1
- package/src/pipeline/phases/production-gate.ts +41 -3
- package/src/pipeline/phases/qa-validation.ts +51 -5
- package/src/pipeline/phases/recovery-loop.ts +229 -7
- package/src/pipeline/phases/review.ts +73 -30
- package/src/pipeline/phases/role-planning.ts +23 -5
- package/src/pipeline/phases/stuck.ts +10 -0
- package/src/pipeline/repo-snapshot.ts +3 -0
- package/src/pipeline/role-execution-adapter.ts +30 -4
- package/src/pipeline/skill-loader.ts +33 -0
- package/src/pipeline/skills/constitution-generator.ts +236 -0
- package/src/pipeline/skills/coverage-gate.ts +199 -0
- package/src/pipeline/skills/generator.ts +287 -0
- package/src/pipeline/skills/role-map.ts +248 -0
- package/src/pipeline/skills/types.ts +53 -0
- package/src/pipeline/skills/usage-registry.ts +87 -0
- package/src/pipeline/strategy-context.ts +60 -0
- package/src/pipeline/type-defs/artifacts.ts +5 -0
- package/src/pipeline/type-defs/checks.ts +4 -0
- package/src/pipeline/type-defs/packets.ts +18 -1
- package/src/pipeline/type-defs/state.ts +26 -1
- package/src/shared/text-utils.ts +70 -0
- package/src/shared/website-strategy-format.ts +56 -0
- package/src/state/index.ts +60 -8
- package/src/types/consensus.ts +1 -0
- package/src/types/workflow.ts +6 -0
- package/src/upgrade/handlers.ts +9 -3
- package/src/workflow/consensus.ts +1 -0
- package/src/workflow/website-strategy.ts +2 -36
- package/src/workflow/website-updater.ts +4 -2
- package/tests/adapters/gemini.test.ts +165 -0
- package/tests/adapters/grok.test.ts +137 -0
- package/tests/adapters/openai.test.ts +128 -0
- package/tests/generators/doc-parser.test.ts +88 -9
- package/tests/generators/quality-gate.test.ts +19 -3
- package/tests/generators/website-components.test.ts +34 -0
- package/tests/generators/website-content-ai.test.ts +308 -0
- package/tests/generators/website-content-scanner.test.ts +86 -0
- package/tests/generators/website-context.test.ts +3 -2
- package/tests/integration/smokestack-scaffold.test.ts +385 -0
- package/tests/pipeline/auto-recovery.test.ts +337 -0
- package/tests/pipeline/change-request.test.ts +70 -0
- package/tests/pipeline/command-resolver.test.ts +42 -0
- package/tests/pipeline/consensus/arbitrator-query.test.ts +107 -0
- package/tests/pipeline/consensus-runner.test.ts +1333 -10
- package/tests/pipeline/consensus-scoring.test.ts +602 -18
- package/tests/pipeline/gate-engine.test.ts +34 -0
- package/tests/pipeline/install-check.test.ts +261 -0
- package/tests/pipeline/migration.test.ts +4 -3
- package/tests/pipeline/orchestrator.test.ts +1506 -15
- package/tests/pipeline/packets/builders.test.ts +29 -6
- package/tests/pipeline/phases/role-planning.strategy.test.ts +204 -0
- package/tests/pipeline/pipeline-persistence.test.ts +230 -0
- package/tests/pipeline/recovery-loop-guidance.test.ts +280 -0
- package/tests/pipeline/role-execution-adapter.test.ts +88 -0
- package/tests/pipeline/skills/constitution-generator.test.ts +201 -0
- package/tests/pipeline/skills/coverage-gate.test.ts +370 -0
- package/tests/pipeline/skills/generator.test.ts +213 -0
- package/tests/pipeline/skills/role-map.test.ts +198 -0
- package/tests/pipeline/skills/usage-registry.test.ts +114 -0
- package/tests/pipeline/strategy-context.test.ts +148 -0
- package/tests/shared/text-utils.test.ts +155 -0
- package/tests/state/progress-analysis.test.ts +375 -0
- package/tests/upgrade/handlers.test.ts +33 -2
- package/tests/workflow/consensus.test.ts +6 -0
- package/tsconfig.json +1 -1
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AI Website Content Generator tests — pricing guard, evidence detection,
|
|
3
|
+
* doc path extraction, and per-field AI fallback.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
7
|
+
import { hasPricingEvidence } from '../../src/generators/website-content-ai.js';
|
|
8
|
+
import {
|
|
9
|
+
extractDocPathsFromText,
|
|
10
|
+
validateAndFilterDocPaths,
|
|
11
|
+
validateWebsiteContext,
|
|
12
|
+
} from '../../src/generators/website-context.js';
|
|
13
|
+
import type { WebsiteContentContext } from '../../src/generators/website-context.js';
|
|
14
|
+
|
|
15
|
+
describe('website-content-ai', () => {
|
|
16
|
+
describe('hasPricingEvidence', () => {
|
|
17
|
+
it('should detect dollar amounts in docs', () => {
|
|
18
|
+
expect(hasPricingEvidence('Plans start at $29/mo', '')).toBe(true);
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it('should detect euro amounts', () => {
|
|
22
|
+
expect(hasPricingEvidence('Starting from €19/month', '')).toBe(true);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it('should detect pricing keywords', () => {
|
|
26
|
+
expect(hasPricingEvidence('Our pricing model includes three tiers', '')).toBe(true);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it('should detect subscription keywords', () => {
|
|
30
|
+
expect(hasPricingEvidence('Monthly subscription available', '')).toBe(true);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it('should detect free plan/tier', () => {
|
|
34
|
+
expect(hasPricingEvidence('We offer a free tier for developers', '')).toBe(true);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it('should detect evidence in specification', () => {
|
|
38
|
+
expect(hasPricingEvidence('', 'Pricing plan: $49/mo')).toBe(true);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it('should return false for docs without pricing info', () => {
|
|
42
|
+
expect(hasPricingEvidence(
|
|
43
|
+
'Our product helps you build amazing software with AI.',
|
|
44
|
+
'A task management tool for teams.',
|
|
45
|
+
)).toBe(false);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it('should return false for empty inputs', () => {
|
|
49
|
+
expect(hasPricingEvidence('', '')).toBe(false);
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
describe('pricing guard edge cases', () => {
|
|
54
|
+
it('should detect /yr pattern', () => {
|
|
55
|
+
expect(hasPricingEvidence('$299/yr for annual plans', '')).toBe(true);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it('should detect per month even without dollar sign', () => {
|
|
59
|
+
// Reason: "per month" is a strong pricing signal regardless of currency symbol
|
|
60
|
+
expect(hasPricingEvidence('10 per month for basic plan', '')).toBe(true);
|
|
61
|
+
expect(hasPricingEvidence('$10 per month for basic plan', '')).toBe(true);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it('should detect USD pattern', () => {
|
|
65
|
+
expect(hasPricingEvidence('USD 29 per month', '')).toBe(true);
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
describe('extractDocPathsFromText', () => {
|
|
70
|
+
it('should extract macOS paths with spaces (quoted)', () => {
|
|
71
|
+
const text = `Build a website for Gateco using '/Users/a b/Gateco/pricing.md'`;
|
|
72
|
+
const paths = extractDocPathsFromText(text);
|
|
73
|
+
expect(paths).toContain('/Users/a b/Gateco/pricing.md');
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it('should extract macOS paths without quotes', () => {
|
|
77
|
+
const text = 'Build a website using /Users/me/Gateco/spec.md please';
|
|
78
|
+
const paths = extractDocPathsFromText(text);
|
|
79
|
+
expect(paths).toContain('/Users/me/Gateco/spec.md');
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it('should extract Windows paths with backslashes (quoted)', () => {
|
|
83
|
+
const text = `Use "C:\\Users\\me\\Gateco\\pricing.md" for content`;
|
|
84
|
+
const paths = extractDocPathsFromText(text);
|
|
85
|
+
expect(paths).toContain('C:\\Users\\me\\Gateco\\pricing.md');
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it('should extract Windows paths with forward slashes', () => {
|
|
89
|
+
const text = `Docs at 'D:/docs/spec.md'`;
|
|
90
|
+
const paths = extractDocPathsFromText(text);
|
|
91
|
+
expect(paths).toContain('D:/docs/spec.md');
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it('should extract multiple paths from mixed text', () => {
|
|
95
|
+
const text = `Build with '/Users/me/spec.md' and "/Users/me/pricing.md"`;
|
|
96
|
+
const paths = extractDocPathsFromText(text);
|
|
97
|
+
expect(paths).toContain('/Users/me/spec.md');
|
|
98
|
+
expect(paths).toContain('/Users/me/pricing.md');
|
|
99
|
+
expect(paths).toHaveLength(2);
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
it('should deduplicate paths', () => {
|
|
103
|
+
const text = `Use '/Users/me/spec.md' and '/Users/me/spec.md' twice`;
|
|
104
|
+
const paths = extractDocPathsFromText(text);
|
|
105
|
+
expect(paths).toHaveLength(1);
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it('should accept .mdx and .txt extensions', () => {
|
|
109
|
+
const text = `Read '/docs/spec.mdx' and '/docs/notes.txt'`;
|
|
110
|
+
const paths = extractDocPathsFromText(text);
|
|
111
|
+
expect(paths).toContain('/docs/spec.mdx');
|
|
112
|
+
expect(paths).toContain('/docs/notes.txt');
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
it('should reject .pdf and .docx extensions', () => {
|
|
116
|
+
const text = `Read '/docs/spec.pdf' and '/docs/notes.docx'`;
|
|
117
|
+
const paths = extractDocPathsFromText(text);
|
|
118
|
+
expect(paths).toHaveLength(0);
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
it('should return empty for text without paths', () => {
|
|
122
|
+
const text = 'Build a website for my project';
|
|
123
|
+
const paths = extractDocPathsFromText(text);
|
|
124
|
+
expect(paths).toHaveLength(0);
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
it('should handle paths at end of string', () => {
|
|
128
|
+
const text = 'Build from /Users/me/spec.md';
|
|
129
|
+
const paths = extractDocPathsFromText(text);
|
|
130
|
+
expect(paths).toContain('/Users/me/spec.md');
|
|
131
|
+
});
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
describe('validateAndFilterDocPaths', () => {
|
|
135
|
+
it('should reject unsupported extensions with log', async () => {
|
|
136
|
+
const logSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
|
|
137
|
+
const result = await validateAndFilterDocPaths(['/tmp/doc.pdf']);
|
|
138
|
+
expect(result).toHaveLength(0);
|
|
139
|
+
expect(logSpy).toHaveBeenCalledWith(
|
|
140
|
+
expect.stringContaining('unsupported format'),
|
|
141
|
+
);
|
|
142
|
+
logSpy.mockRestore();
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
it('should skip non-existent files with log', async () => {
|
|
146
|
+
const logSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
|
|
147
|
+
const result = await validateAndFilterDocPaths(['/tmp/nonexistent-abc-123.md']);
|
|
148
|
+
expect(result).toHaveLength(0);
|
|
149
|
+
expect(logSpy).toHaveBeenCalledWith(
|
|
150
|
+
expect.stringContaining('file not found'),
|
|
151
|
+
);
|
|
152
|
+
logSpy.mockRestore();
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
it('should accept allowed extensions for existing files', async () => {
|
|
156
|
+
// Reason: uses node:fs/promises, so we mock stat
|
|
157
|
+
const { promises: fsp } = await import('node:fs');
|
|
158
|
+
const statSpy = vi.spyOn(fsp, 'stat').mockResolvedValue({ size: 1024 } as any);
|
|
159
|
+
const logSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
|
|
160
|
+
|
|
161
|
+
const result = await validateAndFilterDocPaths(['/tmp/test.md']);
|
|
162
|
+
expect(result).toEqual(['/tmp/test.md']);
|
|
163
|
+
expect(logSpy).toHaveBeenCalledWith(
|
|
164
|
+
expect.stringContaining('[doc-ingest] Read:'),
|
|
165
|
+
);
|
|
166
|
+
|
|
167
|
+
statSpy.mockRestore();
|
|
168
|
+
logSpy.mockRestore();
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
it('should skip oversized files', async () => {
|
|
172
|
+
const { promises: fsp } = await import('node:fs');
|
|
173
|
+
const statSpy = vi.spyOn(fsp, 'stat').mockResolvedValue({
|
|
174
|
+
size: 3 * 1024 * 1024, // 3MB > 2MB limit
|
|
175
|
+
} as any);
|
|
176
|
+
const logSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
|
|
177
|
+
|
|
178
|
+
const result = await validateAndFilterDocPaths(['/tmp/huge.md']);
|
|
179
|
+
expect(result).toHaveLength(0);
|
|
180
|
+
expect(logSpy).toHaveBeenCalledWith(
|
|
181
|
+
expect.stringContaining('exceeds 2MB limit'),
|
|
182
|
+
);
|
|
183
|
+
|
|
184
|
+
statSpy.mockRestore();
|
|
185
|
+
logSpy.mockRestore();
|
|
186
|
+
});
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
describe('per-field AI fallback', () => {
|
|
190
|
+
it('should treat pricing: [] as missing (empty array)', () => {
|
|
191
|
+
// Reason: the fix changes `!context.pricing` to `context.pricing == null || context.pricing.length === 0`
|
|
192
|
+
const emptyPricing: any[] = [];
|
|
193
|
+
const isMissing = emptyPricing == null || emptyPricing.length === 0;
|
|
194
|
+
expect(isMissing).toBe(true);
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
it('should treat pricing: undefined as missing', () => {
|
|
198
|
+
const undefinedPricing = undefined;
|
|
199
|
+
const isMissing = undefinedPricing == null || (undefinedPricing as any)?.length === 0;
|
|
200
|
+
expect(isMissing).toBe(true);
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
it('should not treat populated pricing as missing', () => {
|
|
204
|
+
const pricing = [{ name: 'Pro', price: '$29', description: '', features: [], cta: 'Buy' }];
|
|
205
|
+
const isMissing = pricing == null || pricing.length === 0;
|
|
206
|
+
expect(isMissing).toBe(false);
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
it('should trigger fallback when only pricing is missing', () => {
|
|
210
|
+
// Reason: verifies per-field logic — tagline exists but pricing is empty
|
|
211
|
+
const context = {
|
|
212
|
+
tagline: 'Existing tagline',
|
|
213
|
+
description: 'Existing description',
|
|
214
|
+
features: [{ title: 'A', description: 'B' }],
|
|
215
|
+
pricing: [] as any[],
|
|
216
|
+
};
|
|
217
|
+
const pricingMissing = context.pricing == null || context.pricing.length === 0;
|
|
218
|
+
const hasMissingContent = !context.tagline || !context.description || context.features.length === 0 || pricingMissing;
|
|
219
|
+
expect(hasMissingContent).toBe(true);
|
|
220
|
+
expect(pricingMissing).toBe(true);
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
it('should not trigger fallback when all fields are present', () => {
|
|
224
|
+
const context = {
|
|
225
|
+
tagline: 'Tag',
|
|
226
|
+
description: 'Desc',
|
|
227
|
+
features: [{ title: 'A', description: 'B' }],
|
|
228
|
+
pricing: [{ name: 'Free', price: '$0', description: '', features: [], cta: 'Start' }],
|
|
229
|
+
};
|
|
230
|
+
const pricingMissing = context.pricing == null || context.pricing.length === 0;
|
|
231
|
+
const hasMissingContent = !context.tagline || !context.description || context.features.length === 0 || pricingMissing;
|
|
232
|
+
expect(hasMissingContent).toBe(false);
|
|
233
|
+
});
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
describe('validator provenance-based pricing check', () => {
|
|
237
|
+
it('should NOT warn about default pricing when source is docs', () => {
|
|
238
|
+
const context: WebsiteContentContext = {
|
|
239
|
+
productName: 'Gateco',
|
|
240
|
+
features: [{ title: 'Auth', description: 'Auth feature' }],
|
|
241
|
+
rawDocs: '# Gateco\nSome docs content here for minimum length requirement...',
|
|
242
|
+
pricing: [
|
|
243
|
+
{ name: 'Free', price: 'Free', description: '', features: [], cta: 'Get started' },
|
|
244
|
+
{ name: 'Pro', price: '$99', description: '', features: [], cta: 'Start free trial' },
|
|
245
|
+
{ name: 'Enterprise', price: 'Custom', description: '', features: [], cta: 'Contact sales' },
|
|
246
|
+
],
|
|
247
|
+
pricingDiagnostics: {
|
|
248
|
+
charsScanned: 5000,
|
|
249
|
+
foundPricingHeader: true,
|
|
250
|
+
extractionMethod: 'known_plan_names',
|
|
251
|
+
extractedTiers: [
|
|
252
|
+
{ name: 'Free', price: 'Free' },
|
|
253
|
+
{ name: 'Pro', price: '$99' },
|
|
254
|
+
{ name: 'Enterprise', price: 'Custom' },
|
|
255
|
+
],
|
|
256
|
+
source: 'docs',
|
|
257
|
+
},
|
|
258
|
+
};
|
|
259
|
+
const result = validateWebsiteContext(context, 'Gateco');
|
|
260
|
+
const defaultWarning = result.warnings.find((w) => w.includes('default values'));
|
|
261
|
+
expect(defaultWarning).toBeUndefined();
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
it('should NOT warn about default pricing when source is ai', () => {
|
|
265
|
+
const context: WebsiteContentContext = {
|
|
266
|
+
productName: 'TestApp',
|
|
267
|
+
features: [{ title: 'Feature', description: 'Desc' }],
|
|
268
|
+
rawDocs: '# TestApp\nSome docs content here for minimum length...',
|
|
269
|
+
pricing: [
|
|
270
|
+
{ name: 'Starter', price: '$0', description: '', features: [], cta: 'Get started' },
|
|
271
|
+
{ name: 'Pro', price: '$29', description: '', features: [], cta: 'Start' },
|
|
272
|
+
{ name: 'Enterprise', price: 'Custom', description: '', features: [], cta: 'Contact' },
|
|
273
|
+
],
|
|
274
|
+
pricingDiagnostics: {
|
|
275
|
+
charsScanned: 3000,
|
|
276
|
+
foundPricingHeader: false,
|
|
277
|
+
extractionMethod: 'ai',
|
|
278
|
+
extractedTiers: [
|
|
279
|
+
{ name: 'Starter', price: '$0' },
|
|
280
|
+
{ name: 'Pro', price: '$29' },
|
|
281
|
+
{ name: 'Enterprise', price: 'Custom' },
|
|
282
|
+
],
|
|
283
|
+
source: 'ai',
|
|
284
|
+
},
|
|
285
|
+
};
|
|
286
|
+
const result = validateWebsiteContext(context, 'TestApp');
|
|
287
|
+
const defaultWarning = result.warnings.find((w) => w.includes('default values'));
|
|
288
|
+
expect(defaultWarning).toBeUndefined();
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
it('should warn about default pricing when no diagnostics present', () => {
|
|
292
|
+
const context: WebsiteContentContext = {
|
|
293
|
+
productName: 'TestApp',
|
|
294
|
+
features: [{ title: 'Feature', description: 'Desc' }],
|
|
295
|
+
rawDocs: '# TestApp\nSome docs content here for minimum length...',
|
|
296
|
+
pricing: [
|
|
297
|
+
{ name: 'Starter', price: '$0', description: '', features: [], cta: 'Get started' },
|
|
298
|
+
{ name: 'Pro', price: '$29', description: '', features: [], cta: 'Start' },
|
|
299
|
+
{ name: 'Enterprise', price: 'Custom', description: '', features: [], cta: 'Contact' },
|
|
300
|
+
],
|
|
301
|
+
// No pricingDiagnostics — simulates legacy or missing provenance
|
|
302
|
+
};
|
|
303
|
+
const result = validateWebsiteContext(context, 'TestApp');
|
|
304
|
+
const defaultWarning = result.warnings.find((w) => w.includes('default values'));
|
|
305
|
+
expect(defaultWarning).toBeDefined();
|
|
306
|
+
});
|
|
307
|
+
});
|
|
308
|
+
});
|
|
@@ -178,4 +178,90 @@ export default function Page() {
|
|
|
178
178
|
expect(result.filesScanned).toBe(0);
|
|
179
179
|
expect(result.issues).toHaveLength(0);
|
|
180
180
|
});
|
|
181
|
+
|
|
182
|
+
it('detects "coming soon" text as error', async () => {
|
|
183
|
+
await fs.writeFile(
|
|
184
|
+
path.join(tmpDir, 'src', 'app', 'page.tsx'),
|
|
185
|
+
`export default function Page() {
|
|
186
|
+
return <p>Documentation coming soon...</p>;
|
|
187
|
+
}
|
|
188
|
+
`,
|
|
189
|
+
);
|
|
190
|
+
|
|
191
|
+
const result = await scanGeneratedContent(tmpDir);
|
|
192
|
+
|
|
193
|
+
const comingSoonIssue = result.issues.find((i) => /coming soon/i.test(i.message));
|
|
194
|
+
expect(comingSoonIssue).toBeDefined();
|
|
195
|
+
expect(comingSoonIssue!.severity).toBe('error');
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
it('detects internal link to missing route as error', async () => {
|
|
199
|
+
// Create a page at /src/app/page.tsx (route: /)
|
|
200
|
+
await fs.writeFile(
|
|
201
|
+
path.join(tmpDir, 'src', 'app', 'page.tsx'),
|
|
202
|
+
`export default function Home() {
|
|
203
|
+
return <a href="/nonexistent">Broken Link</a>;
|
|
204
|
+
}
|
|
205
|
+
`,
|
|
206
|
+
);
|
|
207
|
+
|
|
208
|
+
const result = await scanGeneratedContent(tmpDir);
|
|
209
|
+
|
|
210
|
+
const linkIssue = result.issues.find((i) => /internal link.*nonexistent/i.test(i.message));
|
|
211
|
+
expect(linkIssue).toBeDefined();
|
|
212
|
+
expect(linkIssue!.severity).toBe('error');
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
it('passes for internal link to existing route', async () => {
|
|
216
|
+
// Create pages for / and /pricing
|
|
217
|
+
await fs.writeFile(
|
|
218
|
+
path.join(tmpDir, 'src', 'app', 'page.tsx'),
|
|
219
|
+
`export default function Home() {
|
|
220
|
+
return <a href="/pricing">Pricing</a>;
|
|
221
|
+
}
|
|
222
|
+
`,
|
|
223
|
+
);
|
|
224
|
+
await fs.mkdir(path.join(tmpDir, 'src', 'app', 'pricing'), { recursive: true });
|
|
225
|
+
await fs.writeFile(
|
|
226
|
+
path.join(tmpDir, 'src', 'app', 'pricing', 'page.tsx'),
|
|
227
|
+
`export default function Pricing() { return <div>Pricing</div>; }
|
|
228
|
+
`,
|
|
229
|
+
);
|
|
230
|
+
|
|
231
|
+
const result = await scanGeneratedContent(tmpDir);
|
|
232
|
+
|
|
233
|
+
const linkIssue = result.issues.find((i) => /internal link/i.test(i.message));
|
|
234
|
+
expect(linkIssue).toBeUndefined();
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
it('allows anchor links (/#features)', async () => {
|
|
238
|
+
await fs.writeFile(
|
|
239
|
+
path.join(tmpDir, 'src', 'app', 'page.tsx'),
|
|
240
|
+
`export default function Home() {
|
|
241
|
+
return <a href="/#features">Features</a>;
|
|
242
|
+
}
|
|
243
|
+
`,
|
|
244
|
+
);
|
|
245
|
+
|
|
246
|
+
const result = await scanGeneratedContent(tmpDir);
|
|
247
|
+
|
|
248
|
+
const linkIssue = result.issues.find((i) => /internal link/i.test(i.message));
|
|
249
|
+
expect(linkIssue).toBeUndefined();
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
it('ignores external links in internal link scan', async () => {
|
|
253
|
+
await fs.writeFile(
|
|
254
|
+
path.join(tmpDir, 'src', 'app', 'page.tsx'),
|
|
255
|
+
`export default function Home() {
|
|
256
|
+
return <div>No internal links here, just text</div>;
|
|
257
|
+
}
|
|
258
|
+
`,
|
|
259
|
+
);
|
|
260
|
+
|
|
261
|
+
const result = await scanGeneratedContent(tmpDir);
|
|
262
|
+
|
|
263
|
+
// No internal link issues should be reported
|
|
264
|
+
const linkIssue = result.issues.find((i) => /internal link/i.test(i.message));
|
|
265
|
+
expect(linkIssue).toBeUndefined();
|
|
266
|
+
});
|
|
181
267
|
});
|
|
@@ -291,9 +291,10 @@ describe('validateWebsiteContextOrThrow', () => {
|
|
|
291
291
|
expect(result.issues).toEqual([]);
|
|
292
292
|
});
|
|
293
293
|
|
|
294
|
-
it('
|
|
294
|
+
it('does not fail when strategy is missing (strategy validated at injection time)', () => {
|
|
295
295
|
const ctx: WebsiteContentContext = { ...baseContext, strategy: undefined };
|
|
296
|
-
|
|
296
|
+
// Strategy absence is no longer a validation error — it's injected later by the pipeline
|
|
297
|
+
expect(() => validateWebsiteContextOrThrow(ctx, 'gateco')).not.toThrow();
|
|
297
298
|
});
|
|
298
299
|
|
|
299
300
|
it('fails when features are empty', () => {
|