popeye-cli 1.4.7 → 1.6.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 +54 -0
- package/README.md +264 -63
- package/dist/adapters/gemini.d.ts +1 -0
- package/dist/adapters/gemini.d.ts.map +1 -1
- package/dist/adapters/gemini.js +9 -4
- package/dist/adapters/gemini.js.map +1 -1
- package/dist/adapters/grok.d.ts +1 -0
- package/dist/adapters/grok.d.ts.map +1 -1
- package/dist/adapters/grok.js +9 -4
- package/dist/adapters/grok.js.map +1 -1
- package/dist/adapters/openai.d.ts +1 -1
- package/dist/adapters/openai.d.ts.map +1 -1
- package/dist/adapters/openai.js +35 -9
- package/dist/adapters/openai.js.map +1 -1
- package/dist/cli/commands/create.d.ts.map +1 -1
- package/dist/cli/commands/create.js +54 -4
- package/dist/cli/commands/create.js.map +1 -1
- package/dist/cli/interactive.d.ts +29 -0
- package/dist/cli/interactive.d.ts.map +1 -1
- package/dist/cli/interactive.js +132 -7
- package/dist/cli/interactive.js.map +1 -1
- package/dist/generators/all.d.ts +8 -2
- package/dist/generators/all.d.ts.map +1 -1
- package/dist/generators/all.js +37 -316
- package/dist/generators/all.js.map +1 -1
- package/dist/generators/doc-parser.d.ts +64 -0
- package/dist/generators/doc-parser.d.ts.map +1 -0
- package/dist/generators/doc-parser.js +407 -0
- package/dist/generators/doc-parser.js.map +1 -0
- package/dist/generators/frontend-design-analyzer.d.ts +30 -0
- package/dist/generators/frontend-design-analyzer.d.ts.map +1 -0
- package/dist/generators/frontend-design-analyzer.js +208 -0
- package/dist/generators/frontend-design-analyzer.js.map +1 -0
- package/dist/generators/shared-packages.d.ts +45 -0
- package/dist/generators/shared-packages.d.ts.map +1 -0
- package/dist/generators/shared-packages.js +456 -0
- package/dist/generators/shared-packages.js.map +1 -0
- package/dist/generators/templates/index.d.ts +8 -0
- package/dist/generators/templates/index.d.ts.map +1 -1
- package/dist/generators/templates/index.js +8 -0
- package/dist/generators/templates/index.js.map +1 -1
- package/dist/generators/templates/website-components.d.ts +33 -0
- package/dist/generators/templates/website-components.d.ts.map +1 -0
- package/dist/generators/templates/website-components.js +303 -0
- package/dist/generators/templates/website-components.js.map +1 -0
- package/dist/generators/templates/website-config.d.ts +55 -0
- package/dist/generators/templates/website-config.d.ts.map +1 -0
- package/dist/generators/templates/website-config.js +425 -0
- package/dist/generators/templates/website-config.js.map +1 -0
- package/dist/generators/templates/website-conversion.d.ts +27 -0
- package/dist/generators/templates/website-conversion.d.ts.map +1 -0
- package/dist/generators/templates/website-conversion.js +326 -0
- package/dist/generators/templates/website-conversion.js.map +1 -0
- package/dist/generators/templates/website-landing.d.ts +24 -0
- package/dist/generators/templates/website-landing.d.ts.map +1 -0
- package/dist/generators/templates/website-landing.js +276 -0
- package/dist/generators/templates/website-landing.js.map +1 -0
- package/dist/generators/templates/website-layout.d.ts +42 -0
- package/dist/generators/templates/website-layout.d.ts.map +1 -0
- package/dist/generators/templates/website-layout.js +408 -0
- package/dist/generators/templates/website-layout.js.map +1 -0
- package/dist/generators/templates/website-pricing.d.ts +11 -0
- package/dist/generators/templates/website-pricing.d.ts.map +1 -0
- package/dist/generators/templates/website-pricing.js +313 -0
- package/dist/generators/templates/website-pricing.js.map +1 -0
- package/dist/generators/templates/website-sections.d.ts +102 -0
- package/dist/generators/templates/website-sections.d.ts.map +1 -0
- package/dist/generators/templates/website-sections.js +444 -0
- package/dist/generators/templates/website-sections.js.map +1 -0
- package/dist/generators/templates/website-seo.d.ts +76 -0
- package/dist/generators/templates/website-seo.d.ts.map +1 -0
- package/dist/generators/templates/website-seo.js +326 -0
- package/dist/generators/templates/website-seo.js.map +1 -0
- package/dist/generators/templates/website.d.ts +10 -83
- package/dist/generators/templates/website.d.ts.map +1 -1
- package/dist/generators/templates/website.js +12 -875
- package/dist/generators/templates/website.js.map +1 -1
- package/dist/generators/website-content-scanner.d.ts +37 -0
- package/dist/generators/website-content-scanner.d.ts.map +1 -0
- package/dist/generators/website-content-scanner.js +165 -0
- package/dist/generators/website-content-scanner.js.map +1 -0
- package/dist/generators/website-context.d.ts +119 -0
- package/dist/generators/website-context.d.ts.map +1 -0
- package/dist/generators/website-context.js +350 -0
- package/dist/generators/website-context.js.map +1 -0
- package/dist/generators/website-debug.d.ts +68 -0
- package/dist/generators/website-debug.d.ts.map +1 -0
- package/dist/generators/website-debug.js +93 -0
- package/dist/generators/website-debug.js.map +1 -0
- package/dist/generators/website.d.ts +5 -0
- package/dist/generators/website.d.ts.map +1 -1
- package/dist/generators/website.js +136 -11
- package/dist/generators/website.js.map +1 -1
- package/dist/generators/workspace-root.d.ts +27 -0
- package/dist/generators/workspace-root.d.ts.map +1 -0
- package/dist/generators/workspace-root.js +100 -0
- package/dist/generators/workspace-root.js.map +1 -0
- package/dist/state/index.d.ts +35 -0
- package/dist/state/index.d.ts.map +1 -1
- package/dist/state/index.js +40 -0
- package/dist/state/index.js.map +1 -1
- package/dist/types/consensus.d.ts +3 -0
- package/dist/types/consensus.d.ts.map +1 -1
- package/dist/types/consensus.js +1 -0
- package/dist/types/consensus.js.map +1 -1
- package/dist/types/website-strategy.d.ts +263 -0
- package/dist/types/website-strategy.d.ts.map +1 -0
- package/dist/types/website-strategy.js +105 -0
- package/dist/types/website-strategy.js.map +1 -0
- package/dist/types/workflow.d.ts +21 -0
- package/dist/types/workflow.d.ts.map +1 -1
- package/dist/types/workflow.js +8 -0
- package/dist/types/workflow.js.map +1 -1
- package/dist/upgrade/handlers.d.ts +15 -0
- package/dist/upgrade/handlers.d.ts.map +1 -1
- package/dist/upgrade/handlers.js +52 -0
- package/dist/upgrade/handlers.js.map +1 -1
- package/dist/workflow/auto-fix-bundler.d.ts +37 -0
- package/dist/workflow/auto-fix-bundler.d.ts.map +1 -0
- package/dist/workflow/auto-fix-bundler.js +320 -0
- package/dist/workflow/auto-fix-bundler.js.map +1 -0
- package/dist/workflow/auto-fix.d.ts.map +1 -1
- package/dist/workflow/auto-fix.js +10 -3
- package/dist/workflow/auto-fix.js.map +1 -1
- package/dist/workflow/consensus.d.ts.map +1 -1
- package/dist/workflow/consensus.js +2 -0
- package/dist/workflow/consensus.js.map +1 -1
- package/dist/workflow/execution-mode.d.ts.map +1 -1
- package/dist/workflow/execution-mode.js +18 -0
- package/dist/workflow/execution-mode.js.map +1 -1
- package/dist/workflow/index.d.ts +4 -0
- package/dist/workflow/index.d.ts.map +1 -1
- package/dist/workflow/index.js +37 -0
- package/dist/workflow/index.js.map +1 -1
- package/dist/workflow/overview.d.ts +89 -0
- package/dist/workflow/overview.d.ts.map +1 -0
- package/dist/workflow/overview.js +358 -0
- package/dist/workflow/overview.js.map +1 -0
- package/dist/workflow/plan-mode.d.ts +6 -4
- package/dist/workflow/plan-mode.d.ts.map +1 -1
- package/dist/workflow/plan-mode.js +148 -6
- package/dist/workflow/plan-mode.js.map +1 -1
- package/dist/workflow/website-strategy.d.ts +79 -0
- package/dist/workflow/website-strategy.d.ts.map +1 -0
- package/dist/workflow/website-strategy.js +310 -0
- package/dist/workflow/website-strategy.js.map +1 -0
- package/dist/workflow/website-updater.d.ts +17 -0
- package/dist/workflow/website-updater.d.ts.map +1 -0
- package/dist/workflow/website-updater.js +116 -0
- package/dist/workflow/website-updater.js.map +1 -0
- package/dist/workflow/workflow-logger.d.ts +1 -1
- package/dist/workflow/workflow-logger.d.ts.map +1 -1
- package/dist/workflow/workflow-logger.js.map +1 -1
- package/package.json +1 -1
- package/src/adapters/gemini.ts +10 -4
- package/src/adapters/grok.ts +10 -4
- package/src/adapters/openai.ts +38 -6
- package/src/cli/commands/create.ts +58 -4
- package/src/cli/interactive.ts +143 -7
- package/src/generators/all.ts +49 -332
- package/src/generators/doc-parser.ts +449 -0
- package/src/generators/frontend-design-analyzer.ts +261 -0
- package/src/generators/shared-packages.ts +500 -0
- package/src/generators/templates/index.ts +8 -0
- package/src/generators/templates/website-components.ts +330 -0
- package/src/generators/templates/website-config.ts +444 -0
- package/src/generators/templates/website-conversion.ts +341 -0
- package/src/generators/templates/website-landing.ts +331 -0
- package/src/generators/templates/website-layout.ts +443 -0
- package/src/generators/templates/website-pricing.ts +330 -0
- package/src/generators/templates/website-sections.ts +541 -0
- package/src/generators/templates/website-seo.ts +370 -0
- package/src/generators/templates/website.ts +38 -905
- package/src/generators/website-content-scanner.ts +208 -0
- package/src/generators/website-context.ts +493 -0
- package/src/generators/website-debug.ts +130 -0
- package/src/generators/website.ts +178 -20
- package/src/generators/workspace-root.ts +113 -0
- package/src/state/index.ts +56 -0
- package/src/types/consensus.ts +3 -0
- package/src/types/website-strategy.ts +243 -0
- package/src/types/workflow.ts +21 -0
- package/src/upgrade/handlers.ts +65 -0
- package/src/workflow/auto-fix-bundler.ts +392 -0
- package/src/workflow/auto-fix.ts +11 -3
- package/src/workflow/consensus.ts +2 -0
- package/src/workflow/execution-mode.ts +21 -0
- package/src/workflow/index.ts +37 -0
- package/src/workflow/overview.ts +475 -0
- package/src/workflow/plan-mode.ts +193 -8
- package/src/workflow/website-strategy.ts +379 -0
- package/src/workflow/website-updater.ts +142 -0
- package/src/workflow/workflow-logger.ts +1 -0
- package/tests/adapters/persona-switching.test.ts +63 -0
- package/tests/cli/project-naming.test.ts +136 -0
- package/tests/generators/doc-parser.test.ts +121 -0
- package/tests/generators/frontend-design-analyzer.test.ts +90 -0
- package/tests/generators/quality-gate.test.ts +183 -0
- package/tests/generators/shared-packages.test.ts +83 -0
- package/tests/generators/website-components.test.ts +159 -0
- package/tests/generators/website-config.test.ts +84 -0
- package/tests/generators/website-content-scanner.test.ts +181 -0
- package/tests/generators/website-context.test.ts +331 -0
- package/tests/generators/website-debug.test.ts +77 -0
- package/tests/generators/website-landing.test.ts +188 -0
- package/tests/generators/website-pricing.test.ts +98 -0
- package/tests/generators/website-sections.test.ts +245 -0
- package/tests/generators/website-seo-quality.test.ts +246 -0
- package/tests/generators/workspace-root.test.ts +105 -0
- package/tests/upgrade/handlers.test.ts +162 -0
- package/tests/workflow/auto-fix-bundler.test.ts +242 -0
- package/tests/workflow/overview.test.ts +392 -0
- package/tests/workflow/plan-mode.test.ts +111 -1
- package/tests/workflow/website-strategy.test.ts +246 -0
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Website strategy storage, formatting, and hash staleness tests
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
6
|
+
import { promises as fs } from 'node:fs';
|
|
7
|
+
import path from 'node:path';
|
|
8
|
+
import os from 'node:os';
|
|
9
|
+
import {
|
|
10
|
+
storeWebsiteStrategy,
|
|
11
|
+
loadWebsiteStrategy,
|
|
12
|
+
formatStrategyForPlanContext,
|
|
13
|
+
isStrategyStale,
|
|
14
|
+
packProductContext,
|
|
15
|
+
} from '../../src/workflow/website-strategy.js';
|
|
16
|
+
import type {
|
|
17
|
+
WebsiteStrategyDocument,
|
|
18
|
+
StrategyMetadata,
|
|
19
|
+
} from '../../src/types/website-strategy.js';
|
|
20
|
+
|
|
21
|
+
const mockStrategy: WebsiteStrategyDocument = {
|
|
22
|
+
icp: {
|
|
23
|
+
primaryPersona: 'DevOps engineers',
|
|
24
|
+
painPoints: ['Slow pipelines'],
|
|
25
|
+
goals: ['Ship faster'],
|
|
26
|
+
objections: ['Migration effort'],
|
|
27
|
+
},
|
|
28
|
+
positioning: {
|
|
29
|
+
category: 'CI/CD',
|
|
30
|
+
differentiators: ['AI-powered'],
|
|
31
|
+
valueProposition: 'Deploy 10x faster with AI',
|
|
32
|
+
proofPoints: ['500+ teams'],
|
|
33
|
+
},
|
|
34
|
+
messaging: {
|
|
35
|
+
headline: 'Ship Code 10x Faster',
|
|
36
|
+
subheadline: 'AI-Powered CI/CD',
|
|
37
|
+
elevatorPitch: 'Deploy confidently with AI.',
|
|
38
|
+
longDescription: 'AI-powered CI/CD platform.',
|
|
39
|
+
},
|
|
40
|
+
seoStrategy: {
|
|
41
|
+
primaryKeywords: ['CI/CD', 'DevOps'],
|
|
42
|
+
secondaryKeywords: ['deployment'],
|
|
43
|
+
longTailKeywords: ['AI CI/CD platform'],
|
|
44
|
+
titleTemplates: { home: 'Ship Faster', pricing: 'Pricing' },
|
|
45
|
+
metaDescriptions: { home: 'AI CI/CD', pricing: 'Plans' },
|
|
46
|
+
},
|
|
47
|
+
siteArchitecture: {
|
|
48
|
+
pages: [
|
|
49
|
+
{ path: '/', title: 'Home', purpose: 'conversion', pageType: 'landing', sections: ['hero'], seoKeywords: ['ci/cd'], conversionGoal: 'sign up' },
|
|
50
|
+
{ path: '/pricing', title: 'Pricing', purpose: 'monetization', pageType: 'pricing', sections: ['tiers'], seoKeywords: ['pricing'], conversionGoal: 'trial' },
|
|
51
|
+
],
|
|
52
|
+
navigation: [{ label: 'Pricing', href: '/pricing' }],
|
|
53
|
+
footerSections: [{ title: 'Product', links: [{ label: 'Home', href: '/' }] }],
|
|
54
|
+
},
|
|
55
|
+
conversionStrategy: {
|
|
56
|
+
primaryCta: { text: 'Start Trial', href: '/pricing' },
|
|
57
|
+
secondaryCta: { text: 'Docs', href: '/docs' },
|
|
58
|
+
trustSignals: ['SOC 2'],
|
|
59
|
+
socialProof: ['500+ teams'],
|
|
60
|
+
leadCapture: 'webhook',
|
|
61
|
+
},
|
|
62
|
+
competitiveContext: {
|
|
63
|
+
category: 'CI/CD',
|
|
64
|
+
competitors: ['CircleCI'],
|
|
65
|
+
differentiators: ['AI optimization'],
|
|
66
|
+
},
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
const mockMetadata: StrategyMetadata = {
|
|
70
|
+
inputHash: 'abc123',
|
|
71
|
+
generatedAt: new Date().toISOString(),
|
|
72
|
+
version: 1,
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
describe('storeWebsiteStrategy and loadWebsiteStrategy', () => {
|
|
76
|
+
let tempDir: string;
|
|
77
|
+
|
|
78
|
+
beforeEach(async () => {
|
|
79
|
+
tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'popeye-strategy-'));
|
|
80
|
+
await fs.mkdir(path.join(tempDir, '.popeye'), { recursive: true });
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
afterEach(async () => {
|
|
84
|
+
await fs.rm(tempDir, { recursive: true, force: true });
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it('stores and loads strategy with metadata from .popeye/', async () => {
|
|
88
|
+
await storeWebsiteStrategy(tempDir, mockStrategy, mockMetadata);
|
|
89
|
+
|
|
90
|
+
const loaded = await loadWebsiteStrategy(tempDir);
|
|
91
|
+
expect(loaded).not.toBeNull();
|
|
92
|
+
expect(loaded!.strategy.messaging.headline).toBe('Ship Code 10x Faster');
|
|
93
|
+
expect(loaded!.metadata.inputHash).toBe('abc123');
|
|
94
|
+
expect(loaded!.metadata.version).toBe(1);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it('returns null when no strategy file exists', async () => {
|
|
98
|
+
const emptyDir = await fs.mkdtemp(path.join(os.tmpdir(), 'popeye-empty-'));
|
|
99
|
+
await fs.mkdir(path.join(emptyDir, '.popeye'), { recursive: true });
|
|
100
|
+
|
|
101
|
+
const loaded = await loadWebsiteStrategy(emptyDir);
|
|
102
|
+
expect(loaded).toBeNull();
|
|
103
|
+
|
|
104
|
+
await fs.rm(emptyDir, { recursive: true, force: true });
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it('returns null when strategy file has invalid data', async () => {
|
|
108
|
+
const filePath = path.join(tempDir, '.popeye', 'website-strategy.json');
|
|
109
|
+
await fs.writeFile(filePath, '{"strategy": {"invalid": true}}', 'utf-8');
|
|
110
|
+
|
|
111
|
+
const loaded = await loadWebsiteStrategy(tempDir);
|
|
112
|
+
expect(loaded).toBeNull();
|
|
113
|
+
});
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
describe('formatStrategyForPlanContext', () => {
|
|
117
|
+
it('formats strategy as plan context without modifying specification', () => {
|
|
118
|
+
const formatted = formatStrategyForPlanContext(mockStrategy);
|
|
119
|
+
|
|
120
|
+
// Should contain key strategy sections
|
|
121
|
+
expect(formatted).toContain('### Target Customer');
|
|
122
|
+
expect(formatted).toContain('DevOps engineers');
|
|
123
|
+
expect(formatted).toContain('### Positioning');
|
|
124
|
+
expect(formatted).toContain('Deploy 10x faster with AI');
|
|
125
|
+
expect(formatted).toContain('### Messaging');
|
|
126
|
+
expect(formatted).toContain('Ship Code 10x Faster');
|
|
127
|
+
expect(formatted).toContain('### SEO Keywords');
|
|
128
|
+
expect(formatted).toContain('CI/CD');
|
|
129
|
+
expect(formatted).toContain('### Site Architecture');
|
|
130
|
+
expect(formatted).toContain('/ (landing)');
|
|
131
|
+
expect(formatted).toContain('/pricing (pricing)');
|
|
132
|
+
expect(formatted).toContain('### Conversion Strategy');
|
|
133
|
+
expect(formatted).toContain('Start Trial');
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
it('includes all strategy pages in site architecture', () => {
|
|
137
|
+
const formatted = formatStrategyForPlanContext(mockStrategy);
|
|
138
|
+
// Should list all pages from siteArchitecture
|
|
139
|
+
for (const page of mockStrategy.siteArchitecture.pages) {
|
|
140
|
+
expect(formatted).toContain(page.path);
|
|
141
|
+
}
|
|
142
|
+
});
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
describe('isStrategyStale', () => {
|
|
146
|
+
let tempDir: string;
|
|
147
|
+
|
|
148
|
+
beforeEach(async () => {
|
|
149
|
+
tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'popeye-stale-'));
|
|
150
|
+
await fs.mkdir(path.join(tempDir, '.popeye'), { recursive: true });
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
afterEach(async () => {
|
|
154
|
+
await fs.rm(tempDir, { recursive: true, force: true });
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
it('detects stale strategy via inputHash comparison', async () => {
|
|
158
|
+
await storeWebsiteStrategy(tempDir, mockStrategy, mockMetadata);
|
|
159
|
+
|
|
160
|
+
// Different input should be stale
|
|
161
|
+
const stale = await isStrategyStale(tempDir, {
|
|
162
|
+
productContext: 'completely different product',
|
|
163
|
+
projectName: 'different-project',
|
|
164
|
+
brandAssets: { logoOutputPath: 'public/brand/logo.svg' },
|
|
165
|
+
});
|
|
166
|
+
expect(stale).toBe(true);
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
it('returns true (stale) when no strategy file exists', async () => {
|
|
170
|
+
const stale = await isStrategyStale(tempDir, {
|
|
171
|
+
productContext: 'some content',
|
|
172
|
+
projectName: 'test',
|
|
173
|
+
brandAssets: { logoOutputPath: 'public/brand/logo.svg' },
|
|
174
|
+
});
|
|
175
|
+
expect(stale).toBe(true);
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
it('returns cached strategy when hash matches', async () => {
|
|
179
|
+
// Store with a specific hash
|
|
180
|
+
const metadata: StrategyMetadata = {
|
|
181
|
+
...mockMetadata,
|
|
182
|
+
inputHash: 'will-be-computed',
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
// First store, then verify we can detect matching input
|
|
186
|
+
await storeWebsiteStrategy(tempDir, mockStrategy, metadata);
|
|
187
|
+
|
|
188
|
+
const loaded = await loadWebsiteStrategy(tempDir);
|
|
189
|
+
expect(loaded).not.toBeNull();
|
|
190
|
+
expect(loaded!.strategy.messaging.headline).toBe('Ship Code 10x Faster');
|
|
191
|
+
});
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
describe('packProductContext', () => {
|
|
195
|
+
it('preserves high-priority docs (spec, pricing) within budget', () => {
|
|
196
|
+
const context = [
|
|
197
|
+
'--- random-notes.md ---',
|
|
198
|
+
'Some random notes about the project.',
|
|
199
|
+
'',
|
|
200
|
+
'--- product-spec.md ---',
|
|
201
|
+
'# Product Specification\nThis is the product specification.',
|
|
202
|
+
'',
|
|
203
|
+
'--- pricing.md ---',
|
|
204
|
+
'# Pricing\nFree, Pro, Enterprise tiers.',
|
|
205
|
+
'',
|
|
206
|
+
'--- color-scheme.md ---',
|
|
207
|
+
'# Colors\nPrimary: #2563EB',
|
|
208
|
+
].join('\n');
|
|
209
|
+
|
|
210
|
+
const packed = packProductContext(context, 500);
|
|
211
|
+
|
|
212
|
+
// Spec should come first (priority 1)
|
|
213
|
+
const specIndex = packed.indexOf('product-spec.md');
|
|
214
|
+
const pricingIndex = packed.indexOf('pricing.md');
|
|
215
|
+
const randomIndex = packed.indexOf('random-notes.md');
|
|
216
|
+
|
|
217
|
+
expect(specIndex).toBeGreaterThanOrEqual(0);
|
|
218
|
+
expect(pricingIndex).toBeGreaterThanOrEqual(0);
|
|
219
|
+
// Spec should appear before random notes (or random notes may be cut)
|
|
220
|
+
if (randomIndex >= 0) {
|
|
221
|
+
expect(specIndex).toBeLessThan(randomIndex);
|
|
222
|
+
}
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
it('handles context without headers gracefully', () => {
|
|
226
|
+
const context = 'Just raw text without any headers';
|
|
227
|
+
|
|
228
|
+
const packed = packProductContext(context, 100);
|
|
229
|
+
|
|
230
|
+
expect(packed).toBe(context);
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
it('respects budget limit', () => {
|
|
234
|
+
const largeContext = [
|
|
235
|
+
'--- spec.md ---',
|
|
236
|
+
'x'.repeat(5000),
|
|
237
|
+
'',
|
|
238
|
+
'--- pricing.md ---',
|
|
239
|
+
'y'.repeat(5000),
|
|
240
|
+
].join('\n');
|
|
241
|
+
|
|
242
|
+
const packed = packProductContext(largeContext, 1000);
|
|
243
|
+
|
|
244
|
+
expect(packed.length).toBeLessThanOrEqual(1100); // Some tolerance for headers
|
|
245
|
+
});
|
|
246
|
+
});
|