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.
Files changed (214) hide show
  1. package/CHANGELOG.md +54 -0
  2. package/README.md +264 -63
  3. package/dist/adapters/gemini.d.ts +1 -0
  4. package/dist/adapters/gemini.d.ts.map +1 -1
  5. package/dist/adapters/gemini.js +9 -4
  6. package/dist/adapters/gemini.js.map +1 -1
  7. package/dist/adapters/grok.d.ts +1 -0
  8. package/dist/adapters/grok.d.ts.map +1 -1
  9. package/dist/adapters/grok.js +9 -4
  10. package/dist/adapters/grok.js.map +1 -1
  11. package/dist/adapters/openai.d.ts +1 -1
  12. package/dist/adapters/openai.d.ts.map +1 -1
  13. package/dist/adapters/openai.js +35 -9
  14. package/dist/adapters/openai.js.map +1 -1
  15. package/dist/cli/commands/create.d.ts.map +1 -1
  16. package/dist/cli/commands/create.js +54 -4
  17. package/dist/cli/commands/create.js.map +1 -1
  18. package/dist/cli/interactive.d.ts +29 -0
  19. package/dist/cli/interactive.d.ts.map +1 -1
  20. package/dist/cli/interactive.js +132 -7
  21. package/dist/cli/interactive.js.map +1 -1
  22. package/dist/generators/all.d.ts +8 -2
  23. package/dist/generators/all.d.ts.map +1 -1
  24. package/dist/generators/all.js +37 -316
  25. package/dist/generators/all.js.map +1 -1
  26. package/dist/generators/doc-parser.d.ts +64 -0
  27. package/dist/generators/doc-parser.d.ts.map +1 -0
  28. package/dist/generators/doc-parser.js +407 -0
  29. package/dist/generators/doc-parser.js.map +1 -0
  30. package/dist/generators/frontend-design-analyzer.d.ts +30 -0
  31. package/dist/generators/frontend-design-analyzer.d.ts.map +1 -0
  32. package/dist/generators/frontend-design-analyzer.js +208 -0
  33. package/dist/generators/frontend-design-analyzer.js.map +1 -0
  34. package/dist/generators/shared-packages.d.ts +45 -0
  35. package/dist/generators/shared-packages.d.ts.map +1 -0
  36. package/dist/generators/shared-packages.js +456 -0
  37. package/dist/generators/shared-packages.js.map +1 -0
  38. package/dist/generators/templates/index.d.ts +8 -0
  39. package/dist/generators/templates/index.d.ts.map +1 -1
  40. package/dist/generators/templates/index.js +8 -0
  41. package/dist/generators/templates/index.js.map +1 -1
  42. package/dist/generators/templates/website-components.d.ts +33 -0
  43. package/dist/generators/templates/website-components.d.ts.map +1 -0
  44. package/dist/generators/templates/website-components.js +303 -0
  45. package/dist/generators/templates/website-components.js.map +1 -0
  46. package/dist/generators/templates/website-config.d.ts +55 -0
  47. package/dist/generators/templates/website-config.d.ts.map +1 -0
  48. package/dist/generators/templates/website-config.js +425 -0
  49. package/dist/generators/templates/website-config.js.map +1 -0
  50. package/dist/generators/templates/website-conversion.d.ts +27 -0
  51. package/dist/generators/templates/website-conversion.d.ts.map +1 -0
  52. package/dist/generators/templates/website-conversion.js +326 -0
  53. package/dist/generators/templates/website-conversion.js.map +1 -0
  54. package/dist/generators/templates/website-landing.d.ts +24 -0
  55. package/dist/generators/templates/website-landing.d.ts.map +1 -0
  56. package/dist/generators/templates/website-landing.js +276 -0
  57. package/dist/generators/templates/website-landing.js.map +1 -0
  58. package/dist/generators/templates/website-layout.d.ts +42 -0
  59. package/dist/generators/templates/website-layout.d.ts.map +1 -0
  60. package/dist/generators/templates/website-layout.js +408 -0
  61. package/dist/generators/templates/website-layout.js.map +1 -0
  62. package/dist/generators/templates/website-pricing.d.ts +11 -0
  63. package/dist/generators/templates/website-pricing.d.ts.map +1 -0
  64. package/dist/generators/templates/website-pricing.js +313 -0
  65. package/dist/generators/templates/website-pricing.js.map +1 -0
  66. package/dist/generators/templates/website-sections.d.ts +102 -0
  67. package/dist/generators/templates/website-sections.d.ts.map +1 -0
  68. package/dist/generators/templates/website-sections.js +444 -0
  69. package/dist/generators/templates/website-sections.js.map +1 -0
  70. package/dist/generators/templates/website-seo.d.ts +76 -0
  71. package/dist/generators/templates/website-seo.d.ts.map +1 -0
  72. package/dist/generators/templates/website-seo.js +326 -0
  73. package/dist/generators/templates/website-seo.js.map +1 -0
  74. package/dist/generators/templates/website.d.ts +10 -83
  75. package/dist/generators/templates/website.d.ts.map +1 -1
  76. package/dist/generators/templates/website.js +12 -875
  77. package/dist/generators/templates/website.js.map +1 -1
  78. package/dist/generators/website-content-scanner.d.ts +37 -0
  79. package/dist/generators/website-content-scanner.d.ts.map +1 -0
  80. package/dist/generators/website-content-scanner.js +165 -0
  81. package/dist/generators/website-content-scanner.js.map +1 -0
  82. package/dist/generators/website-context.d.ts +119 -0
  83. package/dist/generators/website-context.d.ts.map +1 -0
  84. package/dist/generators/website-context.js +350 -0
  85. package/dist/generators/website-context.js.map +1 -0
  86. package/dist/generators/website-debug.d.ts +68 -0
  87. package/dist/generators/website-debug.d.ts.map +1 -0
  88. package/dist/generators/website-debug.js +93 -0
  89. package/dist/generators/website-debug.js.map +1 -0
  90. package/dist/generators/website.d.ts +5 -0
  91. package/dist/generators/website.d.ts.map +1 -1
  92. package/dist/generators/website.js +136 -11
  93. package/dist/generators/website.js.map +1 -1
  94. package/dist/generators/workspace-root.d.ts +27 -0
  95. package/dist/generators/workspace-root.d.ts.map +1 -0
  96. package/dist/generators/workspace-root.js +100 -0
  97. package/dist/generators/workspace-root.js.map +1 -0
  98. package/dist/state/index.d.ts +35 -0
  99. package/dist/state/index.d.ts.map +1 -1
  100. package/dist/state/index.js +40 -0
  101. package/dist/state/index.js.map +1 -1
  102. package/dist/types/consensus.d.ts +3 -0
  103. package/dist/types/consensus.d.ts.map +1 -1
  104. package/dist/types/consensus.js +1 -0
  105. package/dist/types/consensus.js.map +1 -1
  106. package/dist/types/website-strategy.d.ts +263 -0
  107. package/dist/types/website-strategy.d.ts.map +1 -0
  108. package/dist/types/website-strategy.js +105 -0
  109. package/dist/types/website-strategy.js.map +1 -0
  110. package/dist/types/workflow.d.ts +21 -0
  111. package/dist/types/workflow.d.ts.map +1 -1
  112. package/dist/types/workflow.js +8 -0
  113. package/dist/types/workflow.js.map +1 -1
  114. package/dist/upgrade/handlers.d.ts +15 -0
  115. package/dist/upgrade/handlers.d.ts.map +1 -1
  116. package/dist/upgrade/handlers.js +52 -0
  117. package/dist/upgrade/handlers.js.map +1 -1
  118. package/dist/workflow/auto-fix-bundler.d.ts +37 -0
  119. package/dist/workflow/auto-fix-bundler.d.ts.map +1 -0
  120. package/dist/workflow/auto-fix-bundler.js +320 -0
  121. package/dist/workflow/auto-fix-bundler.js.map +1 -0
  122. package/dist/workflow/auto-fix.d.ts.map +1 -1
  123. package/dist/workflow/auto-fix.js +10 -3
  124. package/dist/workflow/auto-fix.js.map +1 -1
  125. package/dist/workflow/consensus.d.ts.map +1 -1
  126. package/dist/workflow/consensus.js +2 -0
  127. package/dist/workflow/consensus.js.map +1 -1
  128. package/dist/workflow/execution-mode.d.ts.map +1 -1
  129. package/dist/workflow/execution-mode.js +18 -0
  130. package/dist/workflow/execution-mode.js.map +1 -1
  131. package/dist/workflow/index.d.ts +4 -0
  132. package/dist/workflow/index.d.ts.map +1 -1
  133. package/dist/workflow/index.js +37 -0
  134. package/dist/workflow/index.js.map +1 -1
  135. package/dist/workflow/overview.d.ts +89 -0
  136. package/dist/workflow/overview.d.ts.map +1 -0
  137. package/dist/workflow/overview.js +358 -0
  138. package/dist/workflow/overview.js.map +1 -0
  139. package/dist/workflow/plan-mode.d.ts +6 -4
  140. package/dist/workflow/plan-mode.d.ts.map +1 -1
  141. package/dist/workflow/plan-mode.js +148 -6
  142. package/dist/workflow/plan-mode.js.map +1 -1
  143. package/dist/workflow/website-strategy.d.ts +79 -0
  144. package/dist/workflow/website-strategy.d.ts.map +1 -0
  145. package/dist/workflow/website-strategy.js +310 -0
  146. package/dist/workflow/website-strategy.js.map +1 -0
  147. package/dist/workflow/website-updater.d.ts +17 -0
  148. package/dist/workflow/website-updater.d.ts.map +1 -0
  149. package/dist/workflow/website-updater.js +116 -0
  150. package/dist/workflow/website-updater.js.map +1 -0
  151. package/dist/workflow/workflow-logger.d.ts +1 -1
  152. package/dist/workflow/workflow-logger.d.ts.map +1 -1
  153. package/dist/workflow/workflow-logger.js.map +1 -1
  154. package/package.json +1 -1
  155. package/src/adapters/gemini.ts +10 -4
  156. package/src/adapters/grok.ts +10 -4
  157. package/src/adapters/openai.ts +38 -6
  158. package/src/cli/commands/create.ts +58 -4
  159. package/src/cli/interactive.ts +143 -7
  160. package/src/generators/all.ts +49 -332
  161. package/src/generators/doc-parser.ts +449 -0
  162. package/src/generators/frontend-design-analyzer.ts +261 -0
  163. package/src/generators/shared-packages.ts +500 -0
  164. package/src/generators/templates/index.ts +8 -0
  165. package/src/generators/templates/website-components.ts +330 -0
  166. package/src/generators/templates/website-config.ts +444 -0
  167. package/src/generators/templates/website-conversion.ts +341 -0
  168. package/src/generators/templates/website-landing.ts +331 -0
  169. package/src/generators/templates/website-layout.ts +443 -0
  170. package/src/generators/templates/website-pricing.ts +330 -0
  171. package/src/generators/templates/website-sections.ts +541 -0
  172. package/src/generators/templates/website-seo.ts +370 -0
  173. package/src/generators/templates/website.ts +38 -905
  174. package/src/generators/website-content-scanner.ts +208 -0
  175. package/src/generators/website-context.ts +493 -0
  176. package/src/generators/website-debug.ts +130 -0
  177. package/src/generators/website.ts +178 -20
  178. package/src/generators/workspace-root.ts +113 -0
  179. package/src/state/index.ts +56 -0
  180. package/src/types/consensus.ts +3 -0
  181. package/src/types/website-strategy.ts +243 -0
  182. package/src/types/workflow.ts +21 -0
  183. package/src/upgrade/handlers.ts +65 -0
  184. package/src/workflow/auto-fix-bundler.ts +392 -0
  185. package/src/workflow/auto-fix.ts +11 -3
  186. package/src/workflow/consensus.ts +2 -0
  187. package/src/workflow/execution-mode.ts +21 -0
  188. package/src/workflow/index.ts +37 -0
  189. package/src/workflow/overview.ts +475 -0
  190. package/src/workflow/plan-mode.ts +193 -8
  191. package/src/workflow/website-strategy.ts +379 -0
  192. package/src/workflow/website-updater.ts +142 -0
  193. package/src/workflow/workflow-logger.ts +1 -0
  194. package/tests/adapters/persona-switching.test.ts +63 -0
  195. package/tests/cli/project-naming.test.ts +136 -0
  196. package/tests/generators/doc-parser.test.ts +121 -0
  197. package/tests/generators/frontend-design-analyzer.test.ts +90 -0
  198. package/tests/generators/quality-gate.test.ts +183 -0
  199. package/tests/generators/shared-packages.test.ts +83 -0
  200. package/tests/generators/website-components.test.ts +159 -0
  201. package/tests/generators/website-config.test.ts +84 -0
  202. package/tests/generators/website-content-scanner.test.ts +181 -0
  203. package/tests/generators/website-context.test.ts +331 -0
  204. package/tests/generators/website-debug.test.ts +77 -0
  205. package/tests/generators/website-landing.test.ts +188 -0
  206. package/tests/generators/website-pricing.test.ts +98 -0
  207. package/tests/generators/website-sections.test.ts +245 -0
  208. package/tests/generators/website-seo-quality.test.ts +246 -0
  209. package/tests/generators/workspace-root.test.ts +105 -0
  210. package/tests/upgrade/handlers.test.ts +162 -0
  211. package/tests/workflow/auto-fix-bundler.test.ts +242 -0
  212. package/tests/workflow/overview.test.ts +392 -0
  213. package/tests/workflow/plan-mode.test.ts +111 -1
  214. package/tests/workflow/website-strategy.test.ts +246 -0
@@ -0,0 +1,392 @@
1
+ /**
2
+ * Tests for overview module
3
+ * Verifies overview generation, formatting, analysis, and fix capabilities
4
+ */
5
+
6
+ import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
7
+ import { promises as fs } from 'node:fs';
8
+ import path from 'node:path';
9
+ import os from 'node:os';
10
+ import { generateOverview, formatOverview, fixOverviewIssues } from '../../src/workflow/overview.js';
11
+ import type { ProjectOverview, OverviewFixResult } from '../../src/workflow/overview.js';
12
+
13
+ // Mock the state module to avoid filesystem coupling in unit tests
14
+ vi.mock('../../src/state/index.js', async () => {
15
+ const actual = await vi.importActual('../../src/state/index.js');
16
+ return {
17
+ ...actual,
18
+ loadProject: vi.fn(),
19
+ getProgress: vi.fn(),
20
+ storeUserDocs: vi.fn(),
21
+ storeBrandContext: vi.fn(),
22
+ };
23
+ });
24
+
25
+ // Mock website-context to control doc discovery
26
+ vi.mock('../../src/generators/website-context.js', async () => {
27
+ const actual = await vi.importActual('../../src/generators/website-context.js');
28
+ return {
29
+ ...actual,
30
+ discoverProjectDocs: vi.fn().mockResolvedValue([]),
31
+ readProjectDocs: vi.fn().mockResolvedValue(''),
32
+ findBrandAssets: vi.fn().mockResolvedValue({}),
33
+ };
34
+ });
35
+
36
+ // Mock website-updater
37
+ vi.mock('../../src/workflow/website-updater.js', () => ({
38
+ updateWebsiteContent: vi.fn().mockResolvedValue(undefined),
39
+ }));
40
+
41
+ import { loadProject, getProgress, storeUserDocs, storeBrandContext } from '../../src/state/index.js';
42
+ import { discoverProjectDocs, readProjectDocs, findBrandAssets } from '../../src/generators/website-context.js';
43
+
44
+ const mockLoadProject = vi.mocked(loadProject);
45
+ const mockGetProgress = vi.mocked(getProgress);
46
+ const mockStoreUserDocs = vi.mocked(storeUserDocs);
47
+ const mockStoreBrandContext = vi.mocked(storeBrandContext);
48
+ const mockDiscoverDocs = vi.mocked(discoverProjectDocs);
49
+ const mockReadDocs = vi.mocked(readProjectDocs);
50
+ const mockFindBrand = vi.mocked(findBrandAssets);
51
+
52
+ const baseState = {
53
+ id: 'test-id',
54
+ name: 'test-project',
55
+ idea: 'A test project',
56
+ language: 'typescript' as const,
57
+ openaiModel: 'gpt-4o',
58
+ phase: 'execution' as const,
59
+ status: 'in-progress' as const,
60
+ specification: '# Overview\nA great project for testing.\n## Core Features\n- Feature A\n- Feature B',
61
+ plan: '# Plan\n...',
62
+ milestones: [
63
+ {
64
+ id: 'm1',
65
+ name: 'Setup',
66
+ description: 'Initial setup',
67
+ status: 'complete' as const,
68
+ tasks: [
69
+ { id: 't1', name: 'Init project', description: 'Initialize', status: 'complete' as const },
70
+ { id: 't2', name: 'Add config', description: 'Configuration', status: 'complete' as const },
71
+ ],
72
+ },
73
+ {
74
+ id: 'm2',
75
+ name: 'Core Features',
76
+ description: 'Build core features',
77
+ status: 'in-progress' as const,
78
+ tasks: [
79
+ { id: 't3', name: 'Build API', description: 'API endpoints', status: 'complete' as const },
80
+ { id: 't4', name: 'Build UI', description: 'User interface', status: 'pending' as const },
81
+ ],
82
+ },
83
+ ],
84
+ currentMilestone: 'm2',
85
+ currentTask: 't4',
86
+ consensusHistory: [],
87
+ createdAt: '2024-01-01',
88
+ updatedAt: '2024-01-02',
89
+ };
90
+
91
+ describe('generateOverview', () => {
92
+ beforeEach(() => {
93
+ vi.clearAllMocks();
94
+ mockDiscoverDocs.mockResolvedValue([]);
95
+ });
96
+
97
+ it('reads state and produces correct structure', async () => {
98
+ mockLoadProject.mockResolvedValue({
99
+ ...baseState,
100
+ userDocs: '--- spec.md ---\nSome docs',
101
+ brandContext: { primaryColor: '#2563EB' },
102
+ });
103
+
104
+ mockGetProgress.mockResolvedValue({
105
+ totalMilestones: 2,
106
+ completedMilestones: 1,
107
+ totalTasks: 4,
108
+ completedTasks: 3,
109
+ percentComplete: 75,
110
+ });
111
+
112
+ const overview = await generateOverview('/fake/project/dir');
113
+
114
+ expect(overview.name).toBe('test-project');
115
+ expect(overview.language).toBe('typescript');
116
+ expect(overview.phase).toBe('execution');
117
+ expect(overview.plan.totalMilestones).toBe(2);
118
+ expect(overview.plan.totalTasks).toBe(4);
119
+ expect(overview.progress.percentComplete).toBe(75);
120
+ expect(overview.progress.completedTasks).toBe(3);
121
+ expect(overview.userDocs).toEqual(['spec.md']);
122
+ expect(overview.brandContext?.primaryColor).toBe('#2563EB');
123
+ expect(overview.specification.keyFeatures).toContain('Feature A');
124
+ expect(overview.specification.keyFeatures).toContain('Feature B');
125
+ expect(overview.issues).toBeDefined();
126
+ expect(overview.availableDocs).toBeDefined();
127
+ });
128
+
129
+ it('detects missing docs when docs available in CWD', async () => {
130
+ mockLoadProject.mockResolvedValue({ ...baseState });
131
+ mockGetProgress.mockResolvedValue({
132
+ totalMilestones: 2, completedMilestones: 1,
133
+ totalTasks: 4, completedTasks: 3, percentComplete: 75,
134
+ });
135
+ mockDiscoverDocs.mockResolvedValue(['/fake/spec.md', '/fake/pricing.md']);
136
+
137
+ const overview = await generateOverview('/fake/project/dir');
138
+
139
+ expect(overview.availableDocs).toEqual(['spec.md', 'pricing.md']);
140
+ const docsIssue = overview.issues.find((i) => i.category === 'docs');
141
+ expect(docsIssue).toBeDefined();
142
+ expect(docsIssue?.message).toContain('2 doc(s)');
143
+ expect(docsIssue?.fix).toContain('/overview fix');
144
+ });
145
+
146
+ it('detects generic specification', async () => {
147
+ mockLoadProject.mockResolvedValue({
148
+ ...baseState,
149
+ specification: "Here's a comprehensive software specification for your project...",
150
+ });
151
+ mockGetProgress.mockResolvedValue({
152
+ totalMilestones: 2, completedMilestones: 1,
153
+ totalTasks: 4, completedTasks: 3, percentComplete: 75,
154
+ });
155
+
156
+ const overview = await generateOverview('/fake/project/dir');
157
+
158
+ const specIssue = overview.issues.find((i) => i.category === 'spec');
159
+ expect(specIssue).toBeDefined();
160
+ expect(specIssue?.message).toContain('generic AI-generated');
161
+ });
162
+
163
+ it('detects no brand context', async () => {
164
+ mockLoadProject.mockResolvedValue({ ...baseState });
165
+ mockGetProgress.mockResolvedValue({
166
+ totalMilestones: 2, completedMilestones: 1,
167
+ totalTasks: 4, completedTasks: 3, percentComplete: 75,
168
+ });
169
+
170
+ const overview = await generateOverview('/fake/project/dir');
171
+
172
+ const brandIssue = overview.issues.find((i) => i.category === 'brand');
173
+ expect(brandIssue).toBeDefined();
174
+ expect(brandIssue?.message).toContain('No brand context');
175
+ });
176
+
177
+ it('detects very few tasks', async () => {
178
+ mockLoadProject.mockResolvedValue({
179
+ ...baseState,
180
+ milestones: [{
181
+ id: 'm1', name: 'Only', description: 'Only milestone',
182
+ status: 'pending',
183
+ tasks: [{ id: 't1', name: 'Only task', description: 'Only', status: 'pending' }],
184
+ }],
185
+ });
186
+ mockGetProgress.mockResolvedValue({
187
+ totalMilestones: 1, completedMilestones: 0,
188
+ totalTasks: 1, completedTasks: 0, percentComplete: 0,
189
+ });
190
+
191
+ const overview = await generateOverview('/fake/project/dir');
192
+
193
+ const planIssue = overview.issues.find((i) => i.category === 'plan');
194
+ expect(planIssue).toBeDefined();
195
+ expect(planIssue?.message).toContain('only 1 task(s)');
196
+ });
197
+ });
198
+
199
+ describe('formatOverview', () => {
200
+ it('produces formatted string with progress info', () => {
201
+ const overview: ProjectOverview = {
202
+ name: 'my-project',
203
+ idea: 'A test idea',
204
+ language: 'typescript',
205
+ phase: 'execution',
206
+ status: 'in-progress',
207
+ specification: {
208
+ summary: 'A great project',
209
+ keyFeatures: ['Feature A', 'Feature B'],
210
+ },
211
+ plan: {
212
+ totalMilestones: 2,
213
+ totalTasks: 4,
214
+ milestones: [
215
+ {
216
+ name: 'Setup',
217
+ status: 'complete',
218
+ taskCount: 2,
219
+ completedTasks: 2,
220
+ tasks: [
221
+ { name: 'Init', status: 'complete' },
222
+ { name: 'Config', status: 'complete' },
223
+ ],
224
+ },
225
+ {
226
+ name: 'Core',
227
+ status: 'in-progress',
228
+ taskCount: 2,
229
+ completedTasks: 1,
230
+ tasks: [
231
+ { name: 'API', status: 'complete' },
232
+ { name: 'UI', status: 'pending' },
233
+ ],
234
+ },
235
+ ],
236
+ },
237
+ progress: {
238
+ completedMilestones: 1,
239
+ completedTasks: 3,
240
+ percentComplete: 75,
241
+ },
242
+ issues: [],
243
+ availableDocs: [],
244
+ };
245
+
246
+ const output = formatOverview(overview);
247
+
248
+ expect(output).toContain('my-project');
249
+ expect(output).toContain('75%');
250
+ expect(output).toContain('3/4 tasks');
251
+ expect(output).toContain('Setup');
252
+ expect(output).toContain('Core');
253
+ expect(output).toContain('[x]'); // complete status
254
+ expect(output).toContain('[ ]'); // pending status
255
+ });
256
+
257
+ it('handles project with no milestones', () => {
258
+ const overview: ProjectOverview = {
259
+ name: 'empty-project',
260
+ idea: 'Nothing yet',
261
+ language: 'python',
262
+ phase: 'plan',
263
+ status: 'pending',
264
+ specification: {
265
+ summary: '',
266
+ keyFeatures: [],
267
+ },
268
+ plan: {
269
+ totalMilestones: 0,
270
+ totalTasks: 0,
271
+ milestones: [],
272
+ },
273
+ progress: {
274
+ completedMilestones: 0,
275
+ completedTasks: 0,
276
+ percentComplete: 0,
277
+ },
278
+ issues: [],
279
+ availableDocs: [],
280
+ };
281
+
282
+ const output = formatOverview(overview);
283
+
284
+ expect(output).toContain('empty-project');
285
+ expect(output).toContain('0%');
286
+ expect(output).toContain('No milestones defined yet');
287
+ });
288
+
289
+ it('shows analysis section when issues exist', () => {
290
+ const overview: ProjectOverview = {
291
+ name: 'test-project',
292
+ idea: 'Test',
293
+ language: 'website',
294
+ phase: 'complete',
295
+ status: 'complete',
296
+ specification: { summary: 'Test', keyFeatures: [] },
297
+ plan: { totalMilestones: 1, totalTasks: 5, milestones: [] },
298
+ progress: { completedMilestones: 1, completedTasks: 5, percentComplete: 100 },
299
+ issues: [
300
+ { severity: 'warning', category: 'docs', message: 'No docs found', fix: 'Add docs' },
301
+ { severity: 'error', category: 'website', message: 'Placeholder content', fix: 'Run /overview fix' },
302
+ ],
303
+ availableDocs: ['spec.md'],
304
+ };
305
+
306
+ const output = formatOverview(overview);
307
+
308
+ expect(output).toContain('ANALYSIS');
309
+ expect(output).toContain('[!] DOCS: No docs found');
310
+ expect(output).toContain('[!!] WEBSITE: Placeholder content');
311
+ expect(output).toContain('-> Add docs');
312
+ expect(output).toContain('-> Run /overview fix');
313
+ expect(output).toContain('/overview fix');
314
+ });
315
+
316
+ it('shows available docs section when docs not yet imported', () => {
317
+ const overview: ProjectOverview = {
318
+ name: 'test-project',
319
+ idea: 'Test',
320
+ language: 'typescript',
321
+ phase: 'execution',
322
+ status: 'in-progress',
323
+ specification: { summary: 'Test', keyFeatures: [] },
324
+ plan: { totalMilestones: 1, totalTasks: 5, milestones: [] },
325
+ progress: { completedMilestones: 0, completedTasks: 2, percentComplete: 40 },
326
+ issues: [],
327
+ availableDocs: ['spec.md', 'pricing.md'],
328
+ };
329
+
330
+ const output = formatOverview(overview);
331
+
332
+ expect(output).toContain('AVAILABLE DOCS (not yet imported)');
333
+ expect(output).toContain('spec.md');
334
+ expect(output).toContain('pricing.md');
335
+ });
336
+ });
337
+
338
+ describe('fixOverviewIssues', () => {
339
+ beforeEach(() => {
340
+ vi.clearAllMocks();
341
+ });
342
+
343
+ it('discovers docs and stores them', async () => {
344
+ const stateWithDocs = { ...baseState, userDocs: '--- spec.md ---\nContent' };
345
+ mockLoadProject.mockResolvedValue({ ...baseState });
346
+ mockDiscoverDocs.mockResolvedValue(['/parent/spec.md']);
347
+ mockReadDocs.mockResolvedValue('--- spec.md ---\nSpec content');
348
+ mockFindBrand.mockResolvedValue({});
349
+ mockStoreUserDocs.mockResolvedValue(stateWithDocs);
350
+
351
+ const result = await fixOverviewIssues('/parent/project/dir');
352
+
353
+ expect(result.docsDiscovered).toBe(1);
354
+ expect(result.docsStored).toBe(true);
355
+ expect(result.messages.some((m) => m.includes('spec.md'))).toBe(true);
356
+ expect(mockStoreUserDocs).toHaveBeenCalledWith('/parent/project/dir', '--- spec.md ---\nSpec content');
357
+ });
358
+
359
+ it('finds brand assets and extracts colors', async () => {
360
+ const stateWithBrand = {
361
+ ...baseState,
362
+ userDocs: '# Colors\nPrimary: #2563EB',
363
+ brandContext: { logoPath: '/parent/logo.png', primaryColor: '#2563EB' },
364
+ };
365
+ mockLoadProject.mockResolvedValue({ ...baseState });
366
+ mockDiscoverDocs.mockResolvedValue(['/parent/colors.md']);
367
+ mockReadDocs.mockResolvedValue('# Colors\nPrimary: #2563EB');
368
+ mockFindBrand.mockResolvedValue({ logoPath: '/parent/logo.png' });
369
+ mockStoreUserDocs.mockResolvedValue({ ...baseState, userDocs: '# Colors\nPrimary: #2563EB' });
370
+ mockStoreBrandContext
371
+ .mockResolvedValueOnce({ ...baseState, brandContext: { logoPath: '/parent/logo.png' }, userDocs: '# Colors\nPrimary: #2563EB' })
372
+ .mockResolvedValueOnce(stateWithBrand);
373
+
374
+ const result = await fixOverviewIssues('/parent/project/dir');
375
+
376
+ expect(result.brandFound).toBe(true);
377
+ expect(result.messages.some((m) => m.includes('logo.png'))).toBe(true);
378
+ expect(result.messages.some((m) => m.includes('#2563EB'))).toBe(true);
379
+ });
380
+
381
+ it('shows tip when no docs or brand found', async () => {
382
+ mockLoadProject.mockResolvedValue({ ...baseState });
383
+ mockDiscoverDocs.mockResolvedValue([]);
384
+ mockFindBrand.mockResolvedValue({});
385
+
386
+ const result = await fixOverviewIssues('/parent/project/dir');
387
+
388
+ expect(result.docsDiscovered).toBe(0);
389
+ expect(result.brandFound).toBe(false);
390
+ expect(result.messages.some((m) => m.includes('Tip:'))).toBe(true);
391
+ });
392
+ });
@@ -3,7 +3,7 @@
3
3
  */
4
4
 
5
5
  import { describe, it, expect } from 'vitest';
6
- import { parsePlanMilestones } from '../../src/workflow/plan-mode.js';
6
+ import { parsePlanMilestones, parseTaskTag, tagToAppTarget } from '../../src/workflow/plan-mode.js';
7
7
 
8
8
  describe('parsePlanMilestones', () => {
9
9
  describe('with explicit task markers', () => {
@@ -195,6 +195,76 @@ Description: Build the main API endpoints
195
195
  });
196
196
  });
197
197
 
198
+ describe('app tag handling', () => {
199
+ it('should extract tasks with [WEB] tags', () => {
200
+ const plan = `
201
+ ## Milestone 1: Website Branding
202
+
203
+ ### Task 1.1 [WEB]: Update root layout with Gateco branding
204
+ - **Description**: Replace default branding with Gateco colors and logo
205
+
206
+ ### Task 1.2 [WEB]: Create hero section component
207
+ - **Description**: Build the landing page hero with CTA
208
+ `;
209
+
210
+ const milestones = parsePlanMilestones(plan);
211
+ const allTasks = milestones.flatMap(m => m.tasks);
212
+
213
+ expect(allTasks.length).toBeGreaterThanOrEqual(2);
214
+ const taskNames = allTasks.map(t => t.name.toLowerCase());
215
+ expect(taskNames.some(n => n.includes('update root layout'))).toBe(true);
216
+ expect(taskNames.some(n => n.includes('create hero section'))).toBe(true);
217
+ });
218
+
219
+ it('should extract tasks with [INT] tags', () => {
220
+ const plan = `
221
+ ## Milestone 3: Integration
222
+
223
+ ### Task 3.1 [INT]: Wire frontend auth to backend API
224
+ - **Description**: Connect the frontend login form to the backend auth endpoint
225
+
226
+ ### Task 3.2 [INT]: Set up end-to-end test suite
227
+ - **Description**: Create E2E tests covering the full auth flow
228
+ `;
229
+
230
+ const milestones = parsePlanMilestones(plan);
231
+ const allTasks = milestones.flatMap(m => m.tasks);
232
+
233
+ expect(allTasks.length).toBeGreaterThanOrEqual(2);
234
+ const taskNames = allTasks.map(t => t.name.toLowerCase());
235
+ expect(taskNames.some(n => n.includes('wire frontend auth'))).toBe(true);
236
+ expect(taskNames.some(n => n.includes('set up end-to-end'))).toBe(true);
237
+ });
238
+
239
+ it('should extract tasks with mixed app tags across milestones', () => {
240
+ const plan = `
241
+ ## Milestone 1: Setup
242
+
243
+ ### Task 1.1 [FE]: Create React component library
244
+ - **Description**: Scaffold shared components
245
+
246
+ ### Task 1.2 [BE]: Implement REST API endpoints
247
+ - **Description**: Build core API
248
+
249
+ ### Task 1.3 [WEB]: Build marketing landing page
250
+ - **Description**: Create the public-facing website
251
+
252
+ ### Task 1.4 [INT]: Configure CI/CD pipeline
253
+ - **Description**: Set up automated deployment
254
+ `;
255
+
256
+ const milestones = parsePlanMilestones(plan);
257
+ const allTasks = milestones.flatMap(m => m.tasks);
258
+
259
+ expect(allTasks.length).toBeGreaterThanOrEqual(4);
260
+ const taskNames = allTasks.map(t => t.name.toLowerCase());
261
+ expect(taskNames.some(n => n.includes('create react component'))).toBe(true);
262
+ expect(taskNames.some(n => n.includes('implement rest api'))).toBe(true);
263
+ expect(taskNames.some(n => n.includes('build marketing landing'))).toBe(true);
264
+ expect(taskNames.some(n => n.includes('configure ci/cd'))).toBe(true);
265
+ });
266
+ });
267
+
198
268
  describe('fallback behavior', () => {
199
269
  it('should create a default milestone when no tasks found', () => {
200
270
  const plan = `
@@ -211,3 +281,43 @@ The project will do various things but no specific implementation steps are list
211
281
  });
212
282
  });
213
283
  });
284
+
285
+ describe('parseTaskTag', () => {
286
+ it('should return WEB for [WEB] tagged tasks', () => {
287
+ expect(parseTaskTag('[WEB]: Update root layout')).toBe('WEB');
288
+ expect(parseTaskTag('[web]: Update root layout')).toBe('WEB');
289
+ });
290
+
291
+ it('should return FE, BE, INT for their respective tags', () => {
292
+ expect(parseTaskTag('[FE]: Create component')).toBe('FE');
293
+ expect(parseTaskTag('[BE]: Build API')).toBe('BE');
294
+ expect(parseTaskTag('[INT]: Wire frontend to backend')).toBe('INT');
295
+ });
296
+
297
+ it('should return undefined for untagged tasks', () => {
298
+ expect(parseTaskTag('Create component')).toBeUndefined();
299
+ expect(parseTaskTag('Build API endpoints')).toBeUndefined();
300
+ });
301
+
302
+ it('should return undefined for unknown tags', () => {
303
+ expect(parseTaskTag('[UNKNOWN]: Some task')).toBeUndefined();
304
+ });
305
+ });
306
+
307
+ describe('tagToAppTarget', () => {
308
+ it('should map WEB to website', () => {
309
+ expect(tagToAppTarget('WEB')).toBe('website');
310
+ });
311
+
312
+ it('should map FE to frontend', () => {
313
+ expect(tagToAppTarget('FE')).toBe('frontend');
314
+ });
315
+
316
+ it('should map BE to backend', () => {
317
+ expect(tagToAppTarget('BE')).toBe('backend');
318
+ });
319
+
320
+ it('should map INT to unified', () => {
321
+ expect(tagToAppTarget('INT')).toBe('unified');
322
+ });
323
+ });