popeye-cli 2.2.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 +324 -20
- package/dist/cli/interactive.js.map +1 -1
- package/dist/generators/all.d.ts.map +1 -1
- package/dist/generators/all.js +3 -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/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 +26 -7
- package/dist/pipeline/gate-engine.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 +306 -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.map +1 -1
- package/dist/pipeline/phases/intake.js +7 -3
- 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 +18 -2
- 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/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/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 +25 -5
- package/dist/pipeline/type-defs/artifacts.d.ts.map +1 -1
- package/dist/pipeline/type-defs/artifacts.js +4 -0
- package/dist/pipeline/type-defs/artifacts.js.map +1 -1
- package/dist/pipeline/type-defs/audit.d.ts +25 -13
- package/dist/pipeline/type-defs/audit.d.ts.map +1 -1
- package/dist/pipeline/type-defs/checks.d.ts +18 -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 +104 -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 +160 -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 +333 -19
- package/src/generators/all.ts +3 -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/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 +35 -7
- package/src/pipeline/orchestrator.ts +361 -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 +7 -3
- 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 +21 -2
- 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/coverage-gate.ts +199 -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 +4 -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/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/coverage-gate.test.ts +370 -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,155 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for isNoneVariant() and normalizeIssueList()
|
|
3
|
+
* Ensures false-positive blocking issues are filtered while real issues are preserved.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { describe, it, expect } from 'vitest';
|
|
7
|
+
import { isNoneVariant, normalizeIssueList } from '../../src/shared/text-utils.js';
|
|
8
|
+
|
|
9
|
+
describe('isNoneVariant', () => {
|
|
10
|
+
describe('true positives (should be filtered out)', () => {
|
|
11
|
+
it('should detect exact "none" variants', () => {
|
|
12
|
+
expect(isNoneVariant('None')).toBe(true);
|
|
13
|
+
expect(isNoneVariant('none')).toBe(true);
|
|
14
|
+
expect(isNoneVariant('NONE')).toBe(true);
|
|
15
|
+
expect(isNoneVariant('None.')).toBe(true);
|
|
16
|
+
expect(isNoneVariant('N/A')).toBe(true);
|
|
17
|
+
expect(isNoneVariant('n/a')).toBe(true);
|
|
18
|
+
expect(isNoneVariant('NA')).toBe(true);
|
|
19
|
+
expect(isNoneVariant('Nil')).toBe(true);
|
|
20
|
+
expect(isNoneVariant('Nothing')).toBe(true);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it('should detect bullet-prefixed none-variants', () => {
|
|
24
|
+
expect(isNoneVariant('- None')).toBe(true);
|
|
25
|
+
expect(isNoneVariant('* N/A')).toBe(true);
|
|
26
|
+
expect(isNoneVariant('+ Nothing')).toBe(true);
|
|
27
|
+
expect(isNoneVariant('1) Nothing')).toBe(true);
|
|
28
|
+
expect(isNoneVariant('1. None')).toBe(true);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it('should detect "no issues" phrases', () => {
|
|
32
|
+
expect(isNoneVariant('No blocking issues')).toBe(true);
|
|
33
|
+
expect(isNoneVariant('No blocking issues found')).toBe(true);
|
|
34
|
+
expect(isNoneVariant('No issues')).toBe(true);
|
|
35
|
+
expect(isNoneVariant('No issues found')).toBe(true);
|
|
36
|
+
expect(isNoneVariant('No critical issues')).toBe(true);
|
|
37
|
+
expect(isNoneVariant('No blockers')).toBe(true);
|
|
38
|
+
expect(isNoneVariant('No showstoppers')).toBe(true);
|
|
39
|
+
expect(isNoneVariant('No concerns')).toBe(true);
|
|
40
|
+
expect(isNoneVariant('No significant issues')).toBe(true);
|
|
41
|
+
expect(isNoneVariant('No major blocking issues')).toBe(true);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it('should detect "none identified/found" phrases', () => {
|
|
45
|
+
expect(isNoneVariant('None identified')).toBe(true);
|
|
46
|
+
expect(isNoneVariant('None found')).toBe(true);
|
|
47
|
+
expect(isNoneVariant('None detected')).toBe(true);
|
|
48
|
+
expect(isNoneVariant('None at this time')).toBe(true);
|
|
49
|
+
expect(isNoneVariant('None noted')).toBe(true);
|
|
50
|
+
expect(isNoneVariant('None observed')).toBe(true);
|
|
51
|
+
expect(isNoneVariant('None reported')).toBe(true);
|
|
52
|
+
expect(isNoneVariant('None applicable')).toBe(true);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it('should detect "there are no" phrases', () => {
|
|
56
|
+
expect(isNoneVariant('There are no blocking issues')).toBe(true);
|
|
57
|
+
expect(isNoneVariant('There are no significant issues')).toBe(true);
|
|
58
|
+
expect(isNoneVariant('There are no major blocking issues')).toBe(true);
|
|
59
|
+
expect(isNoneVariant('There are no critical blockers')).toBe(true);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it('should handle empty and whitespace strings', () => {
|
|
63
|
+
expect(isNoneVariant('')).toBe(true);
|
|
64
|
+
expect(isNoneVariant(' ')).toBe(true);
|
|
65
|
+
expect(isNoneVariant('\n')).toBe(true);
|
|
66
|
+
expect(isNoneVariant('\t')).toBe(true);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it('should strip trailing punctuation before matching', () => {
|
|
70
|
+
expect(isNoneVariant('None.')).toBe(true);
|
|
71
|
+
expect(isNoneVariant('N/A.')).toBe(true);
|
|
72
|
+
expect(isNoneVariant('No issues!')).toBe(true);
|
|
73
|
+
expect(isNoneVariant('No blocking issues found.')).toBe(true);
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
describe('true negatives (must keep as real issues)', () => {
|
|
78
|
+
it('should keep real blocking issues', () => {
|
|
79
|
+
expect(isNoneVariant('Missing authentication layer')).toBe(false);
|
|
80
|
+
expect(isNoneVariant('SQL injection vulnerability')).toBe(false);
|
|
81
|
+
expect(isNoneVariant('The API has no rate limiting')).toBe(false);
|
|
82
|
+
expect(isNoneVariant('Authentication is not implemented')).toBe(false);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it('should keep "No rollback plan" (not issues/concerns/problems/blockers)', () => {
|
|
86
|
+
expect(isNoneVariant('No rollback plan defined')).toBe(false);
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it('should keep "None of the..." sentences (classic false positive)', () => {
|
|
90
|
+
expect(isNoneVariant('None of the database migrations handle rollback')).toBe(false);
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
describe('edge cases', () => {
|
|
95
|
+
it('should reject long plain text (>80 chars) as real issues', () => {
|
|
96
|
+
const longIssue = 'The authentication system does not properly validate JWT tokens which could lead to unauthorized access to protected endpoints';
|
|
97
|
+
expect(longIssue.length).toBeGreaterThan(80);
|
|
98
|
+
expect(isNoneVariant(longIssue)).toBe(false);
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it('should still detect long none-variant phrases', () => {
|
|
102
|
+
// A "no issues" phrase that happens to be long due to qualifiers
|
|
103
|
+
expect(isNoneVariant('No significant blocking issues or critical concerns identified in this review.')).toBe(true);
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
describe('normalizeIssueList', () => {
|
|
109
|
+
it('should filter out all none-variants from a list', () => {
|
|
110
|
+
const input = [
|
|
111
|
+
'None',
|
|
112
|
+
'No blocking issues found',
|
|
113
|
+
'Missing authentication layer',
|
|
114
|
+
'N/A',
|
|
115
|
+
'SQL injection vulnerability',
|
|
116
|
+
];
|
|
117
|
+
const result = normalizeIssueList(input);
|
|
118
|
+
expect(result).toEqual([
|
|
119
|
+
'Missing authentication layer',
|
|
120
|
+
'SQL injection vulnerability',
|
|
121
|
+
]);
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
it('should handle empty lists', () => {
|
|
125
|
+
expect(normalizeIssueList([])).toEqual([]);
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
it('should filter out empty and whitespace-only items', () => {
|
|
129
|
+
const input = ['', ' ', 'Real issue', '\t'];
|
|
130
|
+
const result = normalizeIssueList(input);
|
|
131
|
+
expect(result).toEqual(['Real issue']);
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
it('should trim items before checking', () => {
|
|
135
|
+
const input = [' None ', ' Missing auth '];
|
|
136
|
+
const result = normalizeIssueList(input);
|
|
137
|
+
expect(result).toEqual(['Missing auth']);
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
it('should preserve order of real issues', () => {
|
|
141
|
+
const input = [
|
|
142
|
+
'None',
|
|
143
|
+
'First real issue',
|
|
144
|
+
'N/A',
|
|
145
|
+
'Second real issue',
|
|
146
|
+
'Third real issue',
|
|
147
|
+
];
|
|
148
|
+
const result = normalizeIssueList(input);
|
|
149
|
+
expect(result).toEqual([
|
|
150
|
+
'First real issue',
|
|
151
|
+
'Second real issue',
|
|
152
|
+
'Third real issue',
|
|
153
|
+
]);
|
|
154
|
+
});
|
|
155
|
+
});
|
|
@@ -0,0 +1,375 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for analyzeProjectProgress and verifyProjectCompletion
|
|
3
|
+
*
|
|
4
|
+
* Validates that the progress analysis correctly handles the edge case
|
|
5
|
+
* where a project is genuinely complete but has no milestone tracking data.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { describe, it, expect, afterEach } from 'vitest';
|
|
9
|
+
import { promises as fs } from 'node:fs';
|
|
10
|
+
import path from 'node:path';
|
|
11
|
+
import os from 'node:os';
|
|
12
|
+
import {
|
|
13
|
+
analyzeProjectProgress,
|
|
14
|
+
verifyProjectCompletion,
|
|
15
|
+
} from '../../src/state/index.js';
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Build a minimal valid ProjectState JSON object.
|
|
19
|
+
*
|
|
20
|
+
* @param overrides - Partial fields to merge on top of the defaults
|
|
21
|
+
* @returns A plain object that satisfies ProjectStateSchema
|
|
22
|
+
*/
|
|
23
|
+
function buildState(overrides: Record<string, unknown> = {}): Record<string, unknown> {
|
|
24
|
+
const now = new Date().toISOString();
|
|
25
|
+
return {
|
|
26
|
+
id: 'test-id',
|
|
27
|
+
name: 'test-project',
|
|
28
|
+
idea: 'A test project',
|
|
29
|
+
language: 'typescript',
|
|
30
|
+
openaiModel: 'gpt-4o',
|
|
31
|
+
phase: 'complete',
|
|
32
|
+
status: 'complete',
|
|
33
|
+
specification: '',
|
|
34
|
+
plan: '',
|
|
35
|
+
milestones: [],
|
|
36
|
+
currentMilestone: null,
|
|
37
|
+
currentTask: null,
|
|
38
|
+
consensusHistory: [],
|
|
39
|
+
createdAt: now,
|
|
40
|
+
updatedAt: now,
|
|
41
|
+
...overrides,
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Write a state.json file inside a temp directory's .popeye/ folder.
|
|
47
|
+
*
|
|
48
|
+
* @param tmpDir - The temporary project root directory
|
|
49
|
+
* @param state - The state object to write
|
|
50
|
+
*/
|
|
51
|
+
async function writeState(tmpDir: string, state: Record<string, unknown>): Promise<void> {
|
|
52
|
+
const stateDir = path.join(tmpDir, '.popeye');
|
|
53
|
+
await fs.mkdir(stateDir, { recursive: true });
|
|
54
|
+
await fs.writeFile(path.join(stateDir, 'state.json'), JSON.stringify(state));
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Write a plan file so readPlanFile can parse it.
|
|
59
|
+
*
|
|
60
|
+
* @param tmpDir - The temporary project root directory
|
|
61
|
+
* @param content - Markdown content for the plan
|
|
62
|
+
*/
|
|
63
|
+
async function writePlan(tmpDir: string, content: string): Promise<void> {
|
|
64
|
+
const docsDir = path.join(tmpDir, 'docs');
|
|
65
|
+
await fs.mkdir(docsDir, { recursive: true });
|
|
66
|
+
await fs.writeFile(path.join(docsDir, 'PLAN.md'), content);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Track temp dirs for cleanup
|
|
70
|
+
const tmpDirs: string[] = [];
|
|
71
|
+
|
|
72
|
+
async function makeTmpDir(): Promise<string> {
|
|
73
|
+
const dir = await fs.mkdtemp(path.join(os.tmpdir(), 'progress-test-'));
|
|
74
|
+
tmpDirs.push(dir);
|
|
75
|
+
return dir;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
afterEach(async () => {
|
|
79
|
+
for (const dir of tmpDirs) {
|
|
80
|
+
await fs.rm(dir, { recursive: true, force: true }).catch(() => {});
|
|
81
|
+
}
|
|
82
|
+
tmpDirs.length = 0;
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
describe('analyzeProjectProgress', () => {
|
|
86
|
+
it('should treat zero milestones + explicitly complete as genuinely complete', async () => {
|
|
87
|
+
const tmpDir = await makeTmpDir();
|
|
88
|
+
await writeState(tmpDir, buildState({
|
|
89
|
+
status: 'complete',
|
|
90
|
+
phase: 'complete',
|
|
91
|
+
milestones: [],
|
|
92
|
+
}));
|
|
93
|
+
|
|
94
|
+
const result = await analyzeProjectProgress(tmpDir);
|
|
95
|
+
|
|
96
|
+
expect(result.isActuallyComplete).toBe(true);
|
|
97
|
+
expect(result.statusMismatch).toBe(false);
|
|
98
|
+
expect(result.totalMilestones).toBe(0);
|
|
99
|
+
expect(result.totalTasks).toBe(0);
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
it('should detect mismatch when zero milestones but phase is not complete', async () => {
|
|
103
|
+
const tmpDir = await makeTmpDir();
|
|
104
|
+
await writeState(tmpDir, buildState({
|
|
105
|
+
status: 'complete',
|
|
106
|
+
phase: 'execution',
|
|
107
|
+
milestones: [],
|
|
108
|
+
}));
|
|
109
|
+
|
|
110
|
+
const result = await analyzeProjectProgress(tmpDir);
|
|
111
|
+
|
|
112
|
+
expect(result.isActuallyComplete).toBe(false);
|
|
113
|
+
expect(result.statusMismatch).toBe(true);
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
it('should treat non-zero milestones with all tasks complete as genuinely complete', async () => {
|
|
117
|
+
const tmpDir = await makeTmpDir();
|
|
118
|
+
await writeState(tmpDir, buildState({
|
|
119
|
+
status: 'complete',
|
|
120
|
+
phase: 'complete',
|
|
121
|
+
milestones: [
|
|
122
|
+
{
|
|
123
|
+
id: 'm1',
|
|
124
|
+
name: 'Milestone 1',
|
|
125
|
+
description: 'First milestone',
|
|
126
|
+
status: 'complete',
|
|
127
|
+
tasks: [
|
|
128
|
+
{ id: 't1', name: 'Task 1', description: 'First task', status: 'complete' },
|
|
129
|
+
{ id: 't2', name: 'Task 2', description: 'Second task', status: 'complete' },
|
|
130
|
+
],
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
id: 'm2',
|
|
134
|
+
name: 'Milestone 2',
|
|
135
|
+
description: 'Second milestone',
|
|
136
|
+
status: 'complete',
|
|
137
|
+
tasks: [
|
|
138
|
+
{ id: 't3', name: 'Task 3', description: 'Third task', status: 'complete' },
|
|
139
|
+
],
|
|
140
|
+
},
|
|
141
|
+
],
|
|
142
|
+
}));
|
|
143
|
+
|
|
144
|
+
const result = await analyzeProjectProgress(tmpDir);
|
|
145
|
+
|
|
146
|
+
expect(result.isActuallyComplete).toBe(true);
|
|
147
|
+
expect(result.statusMismatch).toBe(false);
|
|
148
|
+
expect(result.totalMilestones).toBe(2);
|
|
149
|
+
expect(result.completedTasks).toBe(3);
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
it('should detect mismatch when non-zero milestones are incomplete', async () => {
|
|
153
|
+
const tmpDir = await makeTmpDir();
|
|
154
|
+
await writeState(tmpDir, buildState({
|
|
155
|
+
status: 'complete',
|
|
156
|
+
phase: 'complete',
|
|
157
|
+
milestones: [
|
|
158
|
+
{
|
|
159
|
+
id: 'm1',
|
|
160
|
+
name: 'Milestone 1',
|
|
161
|
+
description: 'First milestone',
|
|
162
|
+
status: 'complete',
|
|
163
|
+
tasks: [
|
|
164
|
+
{ id: 't1', name: 'Task 1', description: 'First task', status: 'complete' },
|
|
165
|
+
],
|
|
166
|
+
},
|
|
167
|
+
{
|
|
168
|
+
id: 'm2',
|
|
169
|
+
name: 'Milestone 2',
|
|
170
|
+
description: 'Second milestone',
|
|
171
|
+
status: 'pending',
|
|
172
|
+
tasks: [
|
|
173
|
+
{ id: 't2', name: 'Task 2', description: 'Second task', status: 'pending' },
|
|
174
|
+
],
|
|
175
|
+
},
|
|
176
|
+
],
|
|
177
|
+
}));
|
|
178
|
+
|
|
179
|
+
const result = await analyzeProjectProgress(tmpDir);
|
|
180
|
+
|
|
181
|
+
expect(result.isActuallyComplete).toBe(false);
|
|
182
|
+
expect(result.statusMismatch).toBe(true);
|
|
183
|
+
expect(result.completedMilestones).toBe(1);
|
|
184
|
+
expect(result.totalMilestones).toBe(2);
|
|
185
|
+
});
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
describe('verifyProjectCompletion', () => {
|
|
189
|
+
it('should return isComplete=true for zero milestones + explicitly complete', async () => {
|
|
190
|
+
const tmpDir = await makeTmpDir();
|
|
191
|
+
await writeState(tmpDir, buildState({
|
|
192
|
+
status: 'complete',
|
|
193
|
+
phase: 'complete',
|
|
194
|
+
milestones: [],
|
|
195
|
+
}));
|
|
196
|
+
|
|
197
|
+
const result = await verifyProjectCompletion(tmpDir);
|
|
198
|
+
|
|
199
|
+
expect(result.isComplete).toBe(true);
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
it('should return isComplete=false when zero milestones but phase is execution', async () => {
|
|
203
|
+
const tmpDir = await makeTmpDir();
|
|
204
|
+
await writeState(tmpDir, buildState({
|
|
205
|
+
status: 'complete',
|
|
206
|
+
phase: 'execution',
|
|
207
|
+
milestones: [],
|
|
208
|
+
}));
|
|
209
|
+
|
|
210
|
+
const result = await verifyProjectCompletion(tmpDir);
|
|
211
|
+
|
|
212
|
+
expect(result.isComplete).toBe(false);
|
|
213
|
+
expect(result.reason).toBeDefined();
|
|
214
|
+
});
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
// ─── Pipeline test helpers ────────────────────────────────
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Build a minimal valid pipeline state object that satisfies PipelineStateSchema.
|
|
221
|
+
*
|
|
222
|
+
* @param overrides - Fields to merge on top of the defaults
|
|
223
|
+
* @returns A plain object matching PipelineState
|
|
224
|
+
*/
|
|
225
|
+
function buildPipelineState(overrides: Record<string, unknown> = {}): Record<string, unknown> {
|
|
226
|
+
return {
|
|
227
|
+
pipelinePhase: 'DONE',
|
|
228
|
+
artifacts: [],
|
|
229
|
+
recoveryCount: 0,
|
|
230
|
+
maxRecoveryIterations: 5,
|
|
231
|
+
gateResults: {},
|
|
232
|
+
gateChecks: {},
|
|
233
|
+
activeRoles: [],
|
|
234
|
+
constitutionHash: '',
|
|
235
|
+
...overrides,
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// ─── Pipeline terminal state handling ─────────────────────
|
|
240
|
+
|
|
241
|
+
describe('pipeline terminal state handling', () => {
|
|
242
|
+
it('should treat pipeline DONE + 0 milestones as genuinely complete', async () => {
|
|
243
|
+
const tmpDir = await makeTmpDir();
|
|
244
|
+
await writeState(tmpDir, buildState({
|
|
245
|
+
status: 'complete',
|
|
246
|
+
phase: 'execution',
|
|
247
|
+
milestones: [],
|
|
248
|
+
pipeline: buildPipelineState({ pipelinePhase: 'DONE' }),
|
|
249
|
+
}));
|
|
250
|
+
|
|
251
|
+
const result = await analyzeProjectProgress(tmpDir);
|
|
252
|
+
|
|
253
|
+
expect(result.isActuallyComplete).toBe(true);
|
|
254
|
+
expect(result.statusMismatch).toBe(false);
|
|
255
|
+
expect(result.pipelineTerminal).toBe(true);
|
|
256
|
+
expect(result.pipelinePhase).toBe('DONE');
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
it('should treat pipeline STUCK + 0 milestones as not complete but no mismatch', async () => {
|
|
260
|
+
const tmpDir = await makeTmpDir();
|
|
261
|
+
await writeState(tmpDir, buildState({
|
|
262
|
+
status: 'complete',
|
|
263
|
+
phase: 'execution',
|
|
264
|
+
milestones: [],
|
|
265
|
+
pipeline: buildPipelineState({
|
|
266
|
+
pipelinePhase: 'STUCK',
|
|
267
|
+
failedPhase: 'QA_VALIDATION',
|
|
268
|
+
recoveryCount: 3,
|
|
269
|
+
}),
|
|
270
|
+
}));
|
|
271
|
+
|
|
272
|
+
const result = await analyzeProjectProgress(tmpDir);
|
|
273
|
+
|
|
274
|
+
expect(result.isActuallyComplete).toBe(false);
|
|
275
|
+
expect(result.statusMismatch).toBe(false);
|
|
276
|
+
expect(result.pipelineTerminal).toBe(true);
|
|
277
|
+
expect(result.progressSummary).toContain('STUCK');
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
it('should treat pipeline active phase + 0 milestones as not terminal', async () => {
|
|
281
|
+
const tmpDir = await makeTmpDir();
|
|
282
|
+
await writeState(tmpDir, buildState({
|
|
283
|
+
status: 'in-progress',
|
|
284
|
+
phase: 'execution',
|
|
285
|
+
milestones: [],
|
|
286
|
+
pipeline: buildPipelineState({ pipelinePhase: 'IMPLEMENTATION' }),
|
|
287
|
+
}));
|
|
288
|
+
|
|
289
|
+
const result = await analyzeProjectProgress(tmpDir);
|
|
290
|
+
|
|
291
|
+
expect(result.pipelineTerminal).toBe(false);
|
|
292
|
+
expect(result.isActuallyComplete).toBe(false);
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
it('should handle exact gateco reproducer (STUCK + complete status + 0 milestones)', async () => {
|
|
296
|
+
const tmpDir = await makeTmpDir();
|
|
297
|
+
await writeState(tmpDir, buildState({
|
|
298
|
+
status: 'complete',
|
|
299
|
+
phase: 'execution',
|
|
300
|
+
milestones: [],
|
|
301
|
+
pipeline: buildPipelineState({
|
|
302
|
+
pipelinePhase: 'STUCK',
|
|
303
|
+
failedPhase: 'QA_VALIDATION',
|
|
304
|
+
recoveryCount: 5,
|
|
305
|
+
}),
|
|
306
|
+
}));
|
|
307
|
+
|
|
308
|
+
const result = await analyzeProjectProgress(tmpDir);
|
|
309
|
+
|
|
310
|
+
// Must NOT trigger status mismatch — pipeline terminal overrides
|
|
311
|
+
expect(result.statusMismatch).toBe(false);
|
|
312
|
+
expect(result.pipelineTerminal).toBe(true);
|
|
313
|
+
expect(result.isActuallyComplete).toBe(false);
|
|
314
|
+
// Summary should contain the stuck details
|
|
315
|
+
expect(result.progressSummary).toContain('STUCK');
|
|
316
|
+
expect(result.progressSummary).toContain('QA_VALIDATION');
|
|
317
|
+
expect(result.progressSummary).toContain('5');
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
it('should treat RECOVERY_LOOP the same as STUCK (interrupted mid-recovery)', async () => {
|
|
321
|
+
const tmpDir = await makeTmpDir();
|
|
322
|
+
await writeState(tmpDir, buildState({
|
|
323
|
+
status: 'complete',
|
|
324
|
+
phase: 'execution',
|
|
325
|
+
milestones: [],
|
|
326
|
+
pipeline: buildPipelineState({
|
|
327
|
+
pipelinePhase: 'RECOVERY_LOOP',
|
|
328
|
+
failedPhase: 'QA_VALIDATION',
|
|
329
|
+
recoveryCount: 1,
|
|
330
|
+
}),
|
|
331
|
+
}));
|
|
332
|
+
|
|
333
|
+
const result = await analyzeProjectProgress(tmpDir);
|
|
334
|
+
|
|
335
|
+
expect(result.statusMismatch).toBe(false);
|
|
336
|
+
expect(result.pipelineTerminal).toBe(true);
|
|
337
|
+
expect(result.isActuallyComplete).toBe(false);
|
|
338
|
+
expect(result.progressSummary).toContain('STUCK');
|
|
339
|
+
expect(result.progressSummary).toContain('QA_VALIDATION');
|
|
340
|
+
});
|
|
341
|
+
});
|
|
342
|
+
|
|
343
|
+
describe('verifyProjectCompletion with pipeline', () => {
|
|
344
|
+
it('should return isComplete=true for pipeline DONE', async () => {
|
|
345
|
+
const tmpDir = await makeTmpDir();
|
|
346
|
+
await writeState(tmpDir, buildState({
|
|
347
|
+
status: 'complete',
|
|
348
|
+
phase: 'execution',
|
|
349
|
+
milestones: [],
|
|
350
|
+
pipeline: buildPipelineState({ pipelinePhase: 'DONE' }),
|
|
351
|
+
}));
|
|
352
|
+
|
|
353
|
+
const result = await verifyProjectCompletion(tmpDir);
|
|
354
|
+
|
|
355
|
+
expect(result.isComplete).toBe(true);
|
|
356
|
+
});
|
|
357
|
+
|
|
358
|
+
it('should return isComplete=false for pipeline STUCK', async () => {
|
|
359
|
+
const tmpDir = await makeTmpDir();
|
|
360
|
+
await writeState(tmpDir, buildState({
|
|
361
|
+
status: 'complete',
|
|
362
|
+
phase: 'execution',
|
|
363
|
+
milestones: [],
|
|
364
|
+
pipeline: buildPipelineState({
|
|
365
|
+
pipelinePhase: 'STUCK',
|
|
366
|
+
failedPhase: 'QA_VALIDATION',
|
|
367
|
+
recoveryCount: 5,
|
|
368
|
+
}),
|
|
369
|
+
}));
|
|
370
|
+
|
|
371
|
+
const result = await verifyProjectCompletion(tmpDir);
|
|
372
|
+
|
|
373
|
+
expect(result.isComplete).toBe(false);
|
|
374
|
+
});
|
|
375
|
+
});
|
|
@@ -13,6 +13,7 @@ vi.mock('../../src/generators/website-context.js', () => ({
|
|
|
13
13
|
buildWebsiteContext: vi.fn(),
|
|
14
14
|
resolveBrandAssets: vi.fn(),
|
|
15
15
|
validateWebsiteContext: vi.fn(),
|
|
16
|
+
extractDocPathsFromText: vi.fn().mockReturnValue([]),
|
|
16
17
|
}));
|
|
17
18
|
|
|
18
19
|
vi.mock('../../src/generators/workspace-root.js', () => ({
|
|
@@ -82,8 +83,8 @@ describe('buildUpgradeContentContext', () => {
|
|
|
82
83
|
expect(context!.features).toHaveLength(1);
|
|
83
84
|
expect(context!.features[0].title).toBe('Feature 1');
|
|
84
85
|
|
|
85
|
-
// Verify buildWebsiteContext was called with
|
|
86
|
-
expect(mockBuildWebsiteContext).toHaveBeenCalledWith(tmpDir, 'TestProject');
|
|
86
|
+
// Verify buildWebsiteContext was called with state specification/idea fallback (null state = undefined)
|
|
87
|
+
expect(mockBuildWebsiteContext).toHaveBeenCalledWith(tmpDir, 'TestProject', undefined, []);
|
|
87
88
|
});
|
|
88
89
|
|
|
89
90
|
it('should apply brand context from state when available', async () => {
|
|
@@ -149,6 +150,36 @@ describe('buildUpgradeContentContext', () => {
|
|
|
149
150
|
expect(warning).toBe('Unknown error building website context');
|
|
150
151
|
});
|
|
151
152
|
|
|
153
|
+
it('should pass specification to buildWebsiteContext when state has it', async () => {
|
|
154
|
+
mockLoadState.mockResolvedValue({
|
|
155
|
+
name: 'TestProject',
|
|
156
|
+
language: 'all',
|
|
157
|
+
specification: 'Expanded specification text',
|
|
158
|
+
idea: 'Original idea text',
|
|
159
|
+
} as any);
|
|
160
|
+
|
|
161
|
+
await buildUpgradeContentContext(tmpDir, 'TestProject');
|
|
162
|
+
|
|
163
|
+
// specification takes priority over idea
|
|
164
|
+
expect(mockBuildWebsiteContext).toHaveBeenCalledWith(
|
|
165
|
+
tmpDir, 'TestProject', 'Expanded specification text', [],
|
|
166
|
+
);
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
it('should fall back to idea when state has no specification', async () => {
|
|
170
|
+
mockLoadState.mockResolvedValue({
|
|
171
|
+
name: 'TestProject',
|
|
172
|
+
language: 'all',
|
|
173
|
+
idea: 'Original idea text',
|
|
174
|
+
} as any);
|
|
175
|
+
|
|
176
|
+
await buildUpgradeContentContext(tmpDir, 'TestProject');
|
|
177
|
+
|
|
178
|
+
expect(mockBuildWebsiteContext).toHaveBeenCalledWith(
|
|
179
|
+
tmpDir, 'TestProject', 'Original idea text', [],
|
|
180
|
+
);
|
|
181
|
+
});
|
|
182
|
+
|
|
152
183
|
it('should work when no state or strategy exists', async () => {
|
|
153
184
|
// Default mocks already return null for state and strategy
|
|
154
185
|
const { context, warning } = await buildUpgradeContentContext(tmpDir, 'TestProject');
|
|
@@ -42,7 +42,9 @@ describe('extractConcerns', () => {
|
|
|
42
42
|
const result: ConsensusResult = {
|
|
43
43
|
score: 80,
|
|
44
44
|
analysis: 'Good plan overall',
|
|
45
|
+
strengths: [],
|
|
45
46
|
concerns: ['Missing error handling', 'No tests defined'],
|
|
47
|
+
blockingIssues: [],
|
|
46
48
|
recommendations: ['Add logging', 'Consider caching'],
|
|
47
49
|
approved: false,
|
|
48
50
|
rawResponse: '',
|
|
@@ -60,6 +62,10 @@ describe('extractConcerns', () => {
|
|
|
60
62
|
const result: ConsensusResult = {
|
|
61
63
|
score: 95,
|
|
62
64
|
analysis: 'Perfect',
|
|
65
|
+
strengths: [],
|
|
66
|
+
concerns: [],
|
|
67
|
+
blockingIssues: [],
|
|
68
|
+
recommendations: [],
|
|
63
69
|
approved: true,
|
|
64
70
|
rawResponse: '',
|
|
65
71
|
};
|