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
package/src/adapters/grok.ts
CHANGED
|
@@ -10,6 +10,7 @@ import type { ConsensusResult, ArbitrationResult } from '../types/consensus.js';
|
|
|
10
10
|
import type { OutputLanguage } from '../types/project.js';
|
|
11
11
|
import { getGrokToken, GROK_API_URL } from '../auth/grok.js';
|
|
12
12
|
import { DEFAULT_GROK_MODEL } from '../types/consensus.js';
|
|
13
|
+
import { normalizeIssueList } from '../shared/text-utils.js';
|
|
13
14
|
|
|
14
15
|
/**
|
|
15
16
|
* Default Grok configuration
|
|
@@ -79,6 +80,44 @@ export async function requestConsensus(
|
|
|
79
80
|
}
|
|
80
81
|
}
|
|
81
82
|
|
|
83
|
+
/**
|
|
84
|
+
* Send a prompt directly to the Grok API and return the raw response string.
|
|
85
|
+
* No prompt wrapping or response parsing — used by the consensus runner
|
|
86
|
+
* which builds its own prompts and parses responses itself.
|
|
87
|
+
*
|
|
88
|
+
* @param prompt - The complete prompt to send
|
|
89
|
+
* @param config - Configuration for model/temperature/maxTokens
|
|
90
|
+
* @returns Raw LLM response text
|
|
91
|
+
*/
|
|
92
|
+
export async function requestRawReview(
|
|
93
|
+
prompt: string,
|
|
94
|
+
config: { model?: string; temperature?: number; maxTokens?: number } = {},
|
|
95
|
+
): Promise<string> {
|
|
96
|
+
const {
|
|
97
|
+
model = DEFAULT_GROK_CONFIG.model,
|
|
98
|
+
temperature = DEFAULT_GROK_CONFIG.temperature,
|
|
99
|
+
maxTokens = DEFAULT_GROK_CONFIG.maxTokens,
|
|
100
|
+
} = config;
|
|
101
|
+
|
|
102
|
+
const client = await createClient();
|
|
103
|
+
|
|
104
|
+
try {
|
|
105
|
+
const completion = await client.chat.completions.create({
|
|
106
|
+
model,
|
|
107
|
+
messages: [{ role: 'user', content: prompt }],
|
|
108
|
+
temperature,
|
|
109
|
+
max_tokens: maxTokens,
|
|
110
|
+
});
|
|
111
|
+
return completion.choices[0]?.message?.content || '';
|
|
112
|
+
} catch (error) {
|
|
113
|
+
if (error instanceof OpenAI.RateLimitError) {
|
|
114
|
+
await sleep(5000);
|
|
115
|
+
return requestRawReview(prompt, config);
|
|
116
|
+
}
|
|
117
|
+
throw error;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
82
121
|
/**
|
|
83
122
|
* Request arbitration from Grok when consensus is stuck
|
|
84
123
|
*
|
|
@@ -144,10 +183,13 @@ STRENGTHS:
|
|
|
144
183
|
- [etc.]
|
|
145
184
|
|
|
146
185
|
CONCERNS:
|
|
147
|
-
- [
|
|
148
|
-
- [
|
|
186
|
+
- [Non-blocking concern 1]
|
|
187
|
+
- [Non-blocking concern 2]
|
|
149
188
|
- [etc.]
|
|
150
189
|
|
|
190
|
+
BLOCKING_ISSUES:
|
|
191
|
+
- [Critical issue that must be resolved, or "None"]
|
|
192
|
+
|
|
151
193
|
RECOMMENDATIONS:
|
|
152
194
|
- [Recommendation 1]
|
|
153
195
|
- [Recommendation 2]
|
|
@@ -253,11 +295,13 @@ export function parseConsensusResponse(response: string): ConsensusResult {
|
|
|
253
295
|
const analysis = extractSection(response, ['ANALYSIS', '## Analysis', '### Analysis']);
|
|
254
296
|
const strengthsText = extractSection(response, ['STRENGTHS', '## Strengths', '### Strengths']);
|
|
255
297
|
const concernsText = extractSection(response, ['CONCERNS', '## Concerns', '### Concerns']);
|
|
298
|
+
const blockingIssuesText = extractSection(response, ['BLOCKING_ISSUES', '## Blocking Issues', '### Blocking Issues']);
|
|
256
299
|
const recommendationsText = extractSection(response, ['RECOMMENDATIONS', '## Recommendations', '### Recommendations']);
|
|
257
300
|
|
|
258
301
|
// Parse lists from sections
|
|
259
302
|
const strengths = parseList(strengthsText);
|
|
260
303
|
const concerns = parseList(concernsText);
|
|
304
|
+
const blockingIssues = normalizeIssueList(parseList(blockingIssuesText));
|
|
261
305
|
const recommendations = parseList(recommendationsText);
|
|
262
306
|
|
|
263
307
|
return {
|
|
@@ -265,6 +309,7 @@ export function parseConsensusResponse(response: string): ConsensusResult {
|
|
|
265
309
|
analysis: analysis.trim(),
|
|
266
310
|
strengths,
|
|
267
311
|
concerns,
|
|
312
|
+
blockingIssues,
|
|
268
313
|
recommendations,
|
|
269
314
|
approved: score >= 95,
|
|
270
315
|
rawResponse: response,
|
|
@@ -295,11 +340,11 @@ function parseArbitrationResponse(response: string): ArbitrationResult {
|
|
|
295
340
|
approved,
|
|
296
341
|
score,
|
|
297
342
|
analysis,
|
|
298
|
-
criticalConcerns: criticalConcerns
|
|
299
|
-
minorConcerns: minorConcerns
|
|
300
|
-
subjectiveConcerns: subjectiveConcerns
|
|
343
|
+
criticalConcerns: normalizeIssueList(criticalConcerns),
|
|
344
|
+
minorConcerns: normalizeIssueList(minorConcerns),
|
|
345
|
+
subjectiveConcerns: normalizeIssueList(subjectiveConcerns),
|
|
301
346
|
reasoning,
|
|
302
|
-
suggestedChanges: suggestedChanges
|
|
347
|
+
suggestedChanges: normalizeIssueList(suggestedChanges),
|
|
303
348
|
rawResponse: response,
|
|
304
349
|
};
|
|
305
350
|
}
|
package/src/adapters/openai.ts
CHANGED
|
@@ -7,6 +7,7 @@ import OpenAI from 'openai';
|
|
|
7
7
|
import type { ConsensusResult, ConsensusConfig, OpenAIModel, OutputLanguage } from '../types/index.js';
|
|
8
8
|
import { getOpenAIToken } from '../auth/index.js';
|
|
9
9
|
import { DEFAULT_CONSENSUS_CONFIG } from '../types/consensus.js';
|
|
10
|
+
import { normalizeIssueList } from '../shared/text-utils.js';
|
|
10
11
|
|
|
11
12
|
/**
|
|
12
13
|
* Create an OpenAI client with stored credentials
|
|
@@ -87,9 +88,10 @@ ${plan}
|
|
|
87
88
|
Please provide:
|
|
88
89
|
1. ANALYSIS: Detailed review of the plan
|
|
89
90
|
2. STRENGTHS: What works well
|
|
90
|
-
3. CONCERNS:
|
|
91
|
-
4.
|
|
92
|
-
5.
|
|
91
|
+
3. CONCERNS: Non-blocking issues, minor gaps, or suggestions for improvement
|
|
92
|
+
4. BLOCKING_ISSUES: Critical issues that MUST be resolved before proceeding (security, data loss, missing requirements, broken architecture). Write "None" if no blocking issues.
|
|
93
|
+
5. RECOMMENDATIONS: Specific improvements
|
|
94
|
+
6. CONSENSUS SCORE: A percentage (0-100%) indicating your agreement
|
|
93
95
|
- 95-100%: Ready for execution
|
|
94
96
|
- 80-94%: Minor revisions needed
|
|
95
97
|
- 60-79%: Significant revisions needed
|
|
@@ -109,15 +111,22 @@ export function parseConsensusResponse(response: string): ConsensusResult {
|
|
|
109
111
|
const scoreMatch = response.match(/CONSENSUS:\s*(\d+)%/i);
|
|
110
112
|
const score = scoreMatch ? parseInt(scoreMatch[1], 10) : 0;
|
|
111
113
|
|
|
112
|
-
// Extract sections
|
|
114
|
+
// Extract sections — handle backward compat when BLOCKING_ISSUES is absent
|
|
113
115
|
const analysis = extractSection(response, 'ANALYSIS', 'STRENGTHS');
|
|
114
116
|
const strengthsText = extractSection(response, 'STRENGTHS', 'CONCERNS');
|
|
115
|
-
const
|
|
117
|
+
const hasBlockingIssues = /BLOCKING_ISSUES[:\s]/i.test(response);
|
|
118
|
+
const concernsText = hasBlockingIssues
|
|
119
|
+
? extractSection(response, 'CONCERNS', 'BLOCKING_ISSUES')
|
|
120
|
+
: extractSection(response, 'CONCERNS', 'RECOMMENDATIONS');
|
|
121
|
+
const blockingIssuesText = hasBlockingIssues
|
|
122
|
+
? extractSection(response, 'BLOCKING_ISSUES', 'RECOMMENDATIONS')
|
|
123
|
+
: '';
|
|
116
124
|
const recommendationsText = extractSection(response, 'RECOMMENDATIONS', 'CONSENSUS');
|
|
117
125
|
|
|
118
126
|
// Parse lists from sections
|
|
119
127
|
const strengths = parseList(strengthsText);
|
|
120
128
|
const concerns = parseList(concernsText);
|
|
129
|
+
const blockingIssues = normalizeIssueList(parseList(blockingIssuesText));
|
|
121
130
|
const recommendations = parseList(recommendationsText);
|
|
122
131
|
|
|
123
132
|
return {
|
|
@@ -125,6 +134,7 @@ export function parseConsensusResponse(response: string): ConsensusResult {
|
|
|
125
134
|
analysis: analysis.trim(),
|
|
126
135
|
strengths,
|
|
127
136
|
concerns,
|
|
137
|
+
blockingIssues,
|
|
128
138
|
recommendations,
|
|
129
139
|
approved: score >= 95,
|
|
130
140
|
rawResponse: response,
|
|
@@ -217,6 +227,44 @@ function sleep(ms: number): Promise<void> {
|
|
|
217
227
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
218
228
|
}
|
|
219
229
|
|
|
230
|
+
/**
|
|
231
|
+
* Send a prompt directly to the OpenAI API and return the raw response string.
|
|
232
|
+
* No prompt wrapping or response parsing — used by the consensus runner
|
|
233
|
+
* which builds its own prompts and parses responses itself.
|
|
234
|
+
*
|
|
235
|
+
* @param prompt - The complete prompt to send
|
|
236
|
+
* @param config - Consensus configuration for model/temperature/maxTokens
|
|
237
|
+
* @returns Raw LLM response text
|
|
238
|
+
*/
|
|
239
|
+
export async function requestRawReview(
|
|
240
|
+
prompt: string,
|
|
241
|
+
config: Partial<ConsensusConfig> = {},
|
|
242
|
+
): Promise<string> {
|
|
243
|
+
const {
|
|
244
|
+
openaiModel = DEFAULT_CONSENSUS_CONFIG.openaiModel,
|
|
245
|
+
temperature = DEFAULT_CONSENSUS_CONFIG.temperature,
|
|
246
|
+
maxTokens = DEFAULT_CONSENSUS_CONFIG.maxTokens,
|
|
247
|
+
} = config;
|
|
248
|
+
|
|
249
|
+
const client = await createClient();
|
|
250
|
+
|
|
251
|
+
try {
|
|
252
|
+
const completion = await client.chat.completions.create({
|
|
253
|
+
model: openaiModel,
|
|
254
|
+
messages: [{ role: 'user', content: prompt }],
|
|
255
|
+
temperature,
|
|
256
|
+
max_tokens: maxTokens,
|
|
257
|
+
});
|
|
258
|
+
return completion.choices[0]?.message?.content || '';
|
|
259
|
+
} catch (error) {
|
|
260
|
+
if (error instanceof OpenAI.RateLimitError) {
|
|
261
|
+
await sleep(5000);
|
|
262
|
+
return requestRawReview(prompt, config);
|
|
263
|
+
}
|
|
264
|
+
throw error;
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
220
268
|
/**
|
|
221
269
|
* Validate that a model is available
|
|
222
270
|
*/
|
|
@@ -241,7 +241,7 @@ export function createCreateCommand(): Command {
|
|
|
241
241
|
printConsensusResult(
|
|
242
242
|
workflowResult.planResult.consensusResult.iterations[
|
|
243
243
|
workflowResult.planResult.consensusResult.iterations.length - 1
|
|
244
|
-
]?.result || { score: 0, analysis: '', approved: false, rawResponse: '' }
|
|
244
|
+
]?.result || { score: 0, analysis: '', approved: false, rawResponse: '', strengths: [], concerns: [], blockingIssues: [], recommendations: [] }
|
|
245
245
|
);
|
|
246
246
|
}
|
|
247
247
|
|
package/src/cli/interactive.ts
CHANGED
|
@@ -293,6 +293,8 @@ interface SessionState {
|
|
|
293
293
|
reviewer: AIProvider;
|
|
294
294
|
arbitrator: AIProvider;
|
|
295
295
|
enableArbitration: boolean;
|
|
296
|
+
/** v2.5.4: Callback to recreate readline after workflow runs (stdin corruption fix) */
|
|
297
|
+
refreshReadline?: () => void;
|
|
296
298
|
}
|
|
297
299
|
|
|
298
300
|
/**
|
|
@@ -317,6 +319,82 @@ function getBuildLabel(language: string): string {
|
|
|
317
319
|
}
|
|
318
320
|
}
|
|
319
321
|
|
|
322
|
+
/**
|
|
323
|
+
* Get language-specific next steps for the project completion summary.
|
|
324
|
+
* Returns an array of instruction strings the user should follow after generation.
|
|
325
|
+
*/
|
|
326
|
+
function getNextSteps(language: string, projectDir: string): string[] {
|
|
327
|
+
const steps: string[] = [];
|
|
328
|
+
|
|
329
|
+
switch (language) {
|
|
330
|
+
case 'website':
|
|
331
|
+
steps.push('cd ' + projectDir);
|
|
332
|
+
steps.push('npm install');
|
|
333
|
+
steps.push('npm run dev # Start the development server');
|
|
334
|
+
steps.push('Open http://localhost:3000 in your browser');
|
|
335
|
+
steps.push('npm run build # Create production build');
|
|
336
|
+
break;
|
|
337
|
+
|
|
338
|
+
case 'typescript':
|
|
339
|
+
case 'javascript':
|
|
340
|
+
steps.push('cd ' + projectDir);
|
|
341
|
+
steps.push('npm install');
|
|
342
|
+
steps.push('npm run dev # Start the development server');
|
|
343
|
+
steps.push('npm test # Run tests');
|
|
344
|
+
steps.push('npm run build # Create production build');
|
|
345
|
+
break;
|
|
346
|
+
|
|
347
|
+
case 'python':
|
|
348
|
+
steps.push('cd ' + projectDir);
|
|
349
|
+
steps.push('python -m venv venv && source venv/bin/activate');
|
|
350
|
+
steps.push('pip install -r requirements.txt');
|
|
351
|
+
steps.push('python main.py # Run the application');
|
|
352
|
+
steps.push('pytest # Run tests');
|
|
353
|
+
break;
|
|
354
|
+
|
|
355
|
+
case 'fullstack':
|
|
356
|
+
steps.push('cd ' + projectDir);
|
|
357
|
+
steps.push('npm install # Install workspace dependencies');
|
|
358
|
+
steps.push('# Frontend:');
|
|
359
|
+
steps.push('cd apps/frontend && npm run dev');
|
|
360
|
+
steps.push('# Backend:');
|
|
361
|
+
steps.push('cd apps/backend && pip install -r requirements.txt && python main.py');
|
|
362
|
+
break;
|
|
363
|
+
|
|
364
|
+
case 'all':
|
|
365
|
+
steps.push('cd ' + projectDir);
|
|
366
|
+
steps.push('npm install # Install workspace dependencies');
|
|
367
|
+
steps.push('# Frontend:');
|
|
368
|
+
steps.push('cd apps/frontend && npm run dev');
|
|
369
|
+
steps.push('# Backend:');
|
|
370
|
+
steps.push('cd apps/backend && pip install -r requirements.txt && python main.py');
|
|
371
|
+
steps.push('# Website:');
|
|
372
|
+
steps.push('cd apps/website && npm run dev');
|
|
373
|
+
break;
|
|
374
|
+
|
|
375
|
+
default:
|
|
376
|
+
steps.push('cd ' + projectDir);
|
|
377
|
+
steps.push('Review the README.md for setup instructions');
|
|
378
|
+
break;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
return steps;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
/**
|
|
385
|
+
* Print next steps section for project completion summary.
|
|
386
|
+
*/
|
|
387
|
+
function printNextSteps(language: string, projectDir: string): void {
|
|
388
|
+
const steps = getNextSteps(language, projectDir);
|
|
389
|
+
console.log();
|
|
390
|
+
console.log(theme.primary.bold(' Next Steps:'));
|
|
391
|
+
for (const step of steps) {
|
|
392
|
+
console.log(` ${theme.dim('$')} ${step}`);
|
|
393
|
+
}
|
|
394
|
+
console.log();
|
|
395
|
+
console.log(` ${theme.dim('Review the generated README.md for full documentation.')}`);
|
|
396
|
+
}
|
|
397
|
+
|
|
320
398
|
/**
|
|
321
399
|
* Draw the header box
|
|
322
400
|
*/
|
|
@@ -2070,12 +2148,37 @@ async function handleResume(state: SessionState, args: string[]): Promise<void>
|
|
|
2070
2148
|
const progressAnalysis = await analyzeProjectProgress(state.projectDir);
|
|
2071
2149
|
const verification = await verifyProjectCompletion(state.projectDir);
|
|
2072
2150
|
|
|
2151
|
+
// v2.5.3: Compute failing gates once for STUCK/RECOVERY_LOOP display.
|
|
2152
|
+
// failedPhase can be misleading (e.g. QA_VALIDATION when the real blocker is
|
|
2153
|
+
// CONSENSUS_MASTER_PLAN). Scan all gateResults for actual failures.
|
|
2154
|
+
const pp = status.state.pipeline?.pipelinePhase;
|
|
2155
|
+
const pl = status.state.pipeline;
|
|
2156
|
+
const failingGates = pl
|
|
2157
|
+
? Object.entries(pl.gateResults)
|
|
2158
|
+
.filter(([, gr]) => !gr.pass)
|
|
2159
|
+
.filter(([, gr]) => gr.blockers.length > 0 || gr.missingArtifacts.length > 0 || gr.failedChecks.length > 0)
|
|
2160
|
+
.sort((a, b) => {
|
|
2161
|
+
const aConsensus = a[1].consensusScore !== undefined ? 0 : 1;
|
|
2162
|
+
const bConsensus = b[1].consensusScore !== undefined ? 0 : 1;
|
|
2163
|
+
if (aConsensus !== bConsensus) return aConsensus - bConsensus;
|
|
2164
|
+
return b[1].blockers.length - a[1].blockers.length;
|
|
2165
|
+
})
|
|
2166
|
+
: [];
|
|
2167
|
+
const blockingAt = failingGates[0]?.[0] ?? pl?.failedPhase ?? 'unknown';
|
|
2168
|
+
|
|
2073
2169
|
console.log();
|
|
2074
2170
|
console.log(theme.primary.bold(' Project Status:'));
|
|
2075
2171
|
console.log(` ${theme.dim('Name:')} ${status.state.name}`);
|
|
2076
2172
|
console.log(` ${theme.dim('Language:')} ${theme.primary(status.state.language)}`);
|
|
2077
|
-
|
|
2078
|
-
|
|
2173
|
+
if (pp === 'STUCK' || pp === 'RECOVERY_LOOP') {
|
|
2174
|
+
// Reason: toLegacyPhase('STUCK') returns 'execution' and status may be stale 'complete'
|
|
2175
|
+
// from a prior completeProject() call — override both with accurate info.
|
|
2176
|
+
console.log(` ${theme.dim('Phase:')} ${theme.error(pp)} (blocking at ${blockingAt})`);
|
|
2177
|
+
console.log(` ${theme.dim('Status:')} ${theme.error('requires intervention')}`);
|
|
2178
|
+
} else {
|
|
2179
|
+
console.log(` ${theme.dim('Phase:')} ${theme.primary(status.state.phase)}`);
|
|
2180
|
+
console.log(` ${theme.dim('Status:')} ${status.state.status}`);
|
|
2181
|
+
}
|
|
2079
2182
|
|
|
2080
2183
|
// Show detailed progress comparison
|
|
2081
2184
|
console.log();
|
|
@@ -2140,8 +2243,85 @@ async function handleResume(state: SessionState, args: string[]): Promise<void>
|
|
|
2140
2243
|
console.log(theme.dim(` Plan file: ${progressAnalysis.planParseError}`));
|
|
2141
2244
|
}
|
|
2142
2245
|
|
|
2143
|
-
//
|
|
2144
|
-
|
|
2246
|
+
// Pipeline-specific messaging takes priority over legacy mismatch
|
|
2247
|
+
// Reason: pp, pl, failingGates, blockingAt already computed above (Change 1)
|
|
2248
|
+
if (pp) {
|
|
2249
|
+
if (pp === 'STUCK') {
|
|
2250
|
+
console.log();
|
|
2251
|
+
console.log(theme.error.bold(' PIPELINE STUCK:'));
|
|
2252
|
+
console.log(theme.error(` Blocking at: ${blockingAt}`));
|
|
2253
|
+
console.log(theme.error(` Recovery attempts: ${pl!.recoveryCount ?? 0}/${pl!.maxRecoveryIterations ?? 5}`));
|
|
2254
|
+
|
|
2255
|
+
// Top-line summary: if the primary failing gate has a consensus score, show it
|
|
2256
|
+
const primaryGate = failingGates[0];
|
|
2257
|
+
if (primaryGate?.[1].consensusScore !== undefined) {
|
|
2258
|
+
const threshold = 0.95;
|
|
2259
|
+
console.log(theme.error(` Consensus score: ${primaryGate[1].consensusScore.toFixed(2)} < ${threshold}`));
|
|
2260
|
+
}
|
|
2261
|
+
|
|
2262
|
+
// Show all failing gates with details
|
|
2263
|
+
if (failingGates.length > 0) {
|
|
2264
|
+
console.log();
|
|
2265
|
+
console.log(theme.warning(' Gate Blockers:'));
|
|
2266
|
+
for (const [phase, gr] of failingGates) {
|
|
2267
|
+
const scoreStr = gr.consensusScore !== undefined
|
|
2268
|
+
? ` (score: ${gr.consensusScore.toFixed(2)})`
|
|
2269
|
+
: '';
|
|
2270
|
+
console.log(theme.warning(` ${phase}${scoreStr}:`));
|
|
2271
|
+
|
|
2272
|
+
if (gr.blockers.length > 0) {
|
|
2273
|
+
for (const blocker of gr.blockers.slice(0, 3)) {
|
|
2274
|
+
console.log(` ${theme.dim('-')} ${blocker}`);
|
|
2275
|
+
}
|
|
2276
|
+
} else if (gr.missingArtifacts.length > 0) {
|
|
2277
|
+
console.log(` ${theme.dim('-')} Missing artifacts: ${gr.missingArtifacts.join(', ')}`);
|
|
2278
|
+
} else if (gr.failedChecks.length > 0) {
|
|
2279
|
+
console.log(` ${theme.dim('-')} Failed checks: ${gr.failedChecks.join(', ')}`);
|
|
2280
|
+
} else {
|
|
2281
|
+
console.log(` ${theme.dim('-')} Gate failed (no details reported)`);
|
|
2282
|
+
}
|
|
2283
|
+
}
|
|
2284
|
+
}
|
|
2285
|
+
|
|
2286
|
+
// Surface diagnostic report paths
|
|
2287
|
+
const stuckReport = pl!.artifacts?.find(a => a.type === 'stuck_report');
|
|
2288
|
+
const rcaReport = pl!.artifacts
|
|
2289
|
+
?.filter(a => a.type === 'rca_report')
|
|
2290
|
+
.sort((a, b) => b.timestamp.localeCompare(a.timestamp))[0];
|
|
2291
|
+
|
|
2292
|
+
if (stuckReport || rcaReport) {
|
|
2293
|
+
console.log();
|
|
2294
|
+
console.log(theme.secondary(' Diagnostic Reports:'));
|
|
2295
|
+
if (stuckReport) console.log(` ${theme.dim('Stuck report:')} ${stuckReport.path}`);
|
|
2296
|
+
if (rcaReport) console.log(` ${theme.dim('RCA report:')} ${rcaReport.path}`);
|
|
2297
|
+
// v2.6.0: Surface auto-recovery artifact and result
|
|
2298
|
+
const autoRecoveryArtifact = pl!.artifacts?.find(a => a.type === 'auto_recovery_guidance');
|
|
2299
|
+
if (autoRecoveryArtifact) {
|
|
2300
|
+
console.log(` ${theme.dim('Auto-recovery:')} ${autoRecoveryArtifact.path}`);
|
|
2301
|
+
}
|
|
2302
|
+
if (pl!.autoRecoveryResult) {
|
|
2303
|
+
console.log(` ${theme.dim('Auto-recovery result:')} ${pl!.autoRecoveryResult}`);
|
|
2304
|
+
}
|
|
2305
|
+
}
|
|
2306
|
+
|
|
2307
|
+
console.log();
|
|
2308
|
+
console.log(theme.secondary(' Provide guidance when resuming to attempt recovery.'));
|
|
2309
|
+
} else if (pp === 'RECOVERY_LOOP') {
|
|
2310
|
+
const count = pl!.recoveryCount ?? 0;
|
|
2311
|
+
const max = pl!.maxRecoveryIterations ?? 5;
|
|
2312
|
+
console.log();
|
|
2313
|
+
console.log(theme.warning.bold(' RECOVERY IN PROGRESS:'));
|
|
2314
|
+
console.log(theme.warning(` Failed at: ${pl!.failedPhase ?? 'unknown'}`));
|
|
2315
|
+
console.log(theme.warning(` Recovery attempts: ${count}/${max}`));
|
|
2316
|
+
if (count < max) {
|
|
2317
|
+
console.log(theme.secondary(' Resume will auto-retry. Optional: add guidance to steer recovery.'));
|
|
2318
|
+
console.log(theme.secondary(' Once attempts are exhausted, guidance will be required.'));
|
|
2319
|
+
} else {
|
|
2320
|
+
console.log(theme.secondary(' All attempts exhausted. Add guidance when resuming to retry.'));
|
|
2321
|
+
}
|
|
2322
|
+
}
|
|
2323
|
+
} else if (progressAnalysis.statusMismatch && !progressAnalysis.planMismatch) {
|
|
2324
|
+
// Legacy mismatch (non-pipeline projects only)
|
|
2145
2325
|
console.log();
|
|
2146
2326
|
console.log(theme.warning.bold(' WARNING: Status Mismatch Detected!'));
|
|
2147
2327
|
console.log(theme.warning(` Project status says '${status.state.status}' but work is incomplete.`));
|
|
@@ -2199,15 +2379,22 @@ async function handleResume(state: SessionState, args: string[]): Promise<void>
|
|
|
2199
2379
|
// successful build verification (sets status='complete', phase='complete').
|
|
2200
2380
|
// If all tasks are done but status is still 'in-progress', the final
|
|
2201
2381
|
// verification phase (build, tests, README) never completed successfully.
|
|
2202
|
-
|
|
2382
|
+
const pipelineDone = status.state.pipeline?.pipelinePhase === 'DONE';
|
|
2383
|
+
if ((verification.isComplete && projectExplicitlyCompleted) ||
|
|
2384
|
+
(pipelineDone && progressAnalysis.totalMilestones === 0)) {
|
|
2203
2385
|
console.log();
|
|
2204
2386
|
printSuccess('Project is fully complete!');
|
|
2205
2387
|
console.log();
|
|
2206
2388
|
console.log(theme.primary.bold(' Project Summary:'));
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
|
|
2389
|
+
if (pipelineDone && progressAnalysis.totalMilestones === 0) {
|
|
2390
|
+
console.log(` ${theme.dim('Pipeline:')} ${theme.success('completed')}`);
|
|
2391
|
+
} else {
|
|
2392
|
+
console.log(` ${theme.dim('Milestones:')} ${progressAnalysis.totalMilestones}/${progressAnalysis.totalMilestones} complete`);
|
|
2393
|
+
console.log(` ${theme.dim('Tasks:')} ${progressAnalysis.totalTasks}/${progressAnalysis.totalTasks} complete (100%)`);
|
|
2394
|
+
console.log(` ${theme.dim('Build:')} ${theme.success(`${buildLabel} build passed`)}`);
|
|
2395
|
+
}
|
|
2210
2396
|
console.log(` ${theme.dim('Location:')} ${state.projectDir}`);
|
|
2397
|
+
printNextSteps(status.state.language, state.projectDir);
|
|
2211
2398
|
return;
|
|
2212
2399
|
}
|
|
2213
2400
|
|
|
@@ -2220,9 +2407,51 @@ async function handleResume(state: SessionState, args: string[]): Promise<void>
|
|
|
2220
2407
|
|
|
2221
2408
|
// Check if user provided context as argument
|
|
2222
2409
|
let additionalContext = args.join(' ').trim();
|
|
2410
|
+
const isStuck = pp === 'STUCK';
|
|
2223
2411
|
|
|
2224
2412
|
// If no context provided, ask if they want to add guidance
|
|
2225
|
-
|
|
2413
|
+
// v2.5.3: STUCK pipelines get context-aware prompt with blocker details and phase-specific hints
|
|
2414
|
+
if (!additionalContext && isStuck) {
|
|
2415
|
+
if (failingGates.length > 0) {
|
|
2416
|
+
console.log();
|
|
2417
|
+
console.log(theme.warning.bold(' Guidance should address:'));
|
|
2418
|
+
for (const [phase, gr] of failingGates) {
|
|
2419
|
+
for (const b of gr.blockers.slice(0, 2)) {
|
|
2420
|
+
console.log(` ${theme.dim('-')} [${phase}] ${b}`);
|
|
2421
|
+
}
|
|
2422
|
+
}
|
|
2423
|
+
}
|
|
2424
|
+
|
|
2425
|
+
// Phase-specific actionable hints
|
|
2426
|
+
let promptHint = 'e.g., "Focus on fixing the specific blocker above"';
|
|
2427
|
+
if (blockingAt.startsWith('CONSENSUS_')) {
|
|
2428
|
+
promptHint = 'e.g., "Reduce scope to core features only", "Split into smaller milestones", "Lower the consensus threshold"';
|
|
2429
|
+
} else if (blockingAt === 'QA_VALIDATION') {
|
|
2430
|
+
promptHint = 'e.g., "Fix failing test X", "Skip flaky tests", "Adjust build config"';
|
|
2431
|
+
} else if (blockingAt === 'PRODUCTION_GATE') {
|
|
2432
|
+
promptHint = 'e.g., "Mark finding as non-blocking", "Implement quick fix for issue X"';
|
|
2433
|
+
}
|
|
2434
|
+
|
|
2435
|
+
console.log();
|
|
2436
|
+
// Default YES — STUCK pipelines strongly need guidance
|
|
2437
|
+
const wantsContext = failingGates.length > 0
|
|
2438
|
+
? await promptYesNo(theme.primary('Would you like to provide guidance to unblock the pipeline?'), true)
|
|
2439
|
+
: await promptYesNo(theme.primary('Would you like to add guidance before resuming?'), false);
|
|
2440
|
+
|
|
2441
|
+
if (wantsContext) {
|
|
2442
|
+
additionalContext = await promptForContext(
|
|
2443
|
+
`What guidance would you like to give? (${promptHint})`
|
|
2444
|
+
);
|
|
2445
|
+
} else {
|
|
2446
|
+
// v2.6.0: Don't return early — let the orchestrator attempt auto-recovery
|
|
2447
|
+
// via the arbitrator before giving up. If auto-recovery fails,
|
|
2448
|
+
// resumePipeline() returns STUCK and the normal error path handles it.
|
|
2449
|
+
console.log();
|
|
2450
|
+
console.log(theme.secondary(' No guidance provided. Attempting auto-recovery...'));
|
|
2451
|
+
// additionalContext stays empty — resumePipeline will attempt auto-recovery
|
|
2452
|
+
}
|
|
2453
|
+
} else if (!additionalContext) {
|
|
2454
|
+
// Non-STUCK: existing generic guidance prompt
|
|
2226
2455
|
console.log();
|
|
2227
2456
|
const wantsContext = await promptYesNo(
|
|
2228
2457
|
theme.primary('Would you like to add guidance before resuming?'),
|
|
@@ -2301,6 +2530,8 @@ async function handleResume(state: SessionState, args: string[]): Promise<void>
|
|
|
2301
2530
|
} else if (testSt === 'no-tests') {
|
|
2302
2531
|
console.log(` ${theme.dim('Tests:')} ${theme.dim('No tests found')}`);
|
|
2303
2532
|
}
|
|
2533
|
+
|
|
2534
|
+
printNextSteps(status.state.language, state.projectDir);
|
|
2304
2535
|
} else if (result.rateLimitPaused) {
|
|
2305
2536
|
// Rate limit pause - show friendly message, not an error
|
|
2306
2537
|
console.log();
|
|
@@ -2320,8 +2551,27 @@ async function handleResume(state: SessionState, args: string[]): Promise<void>
|
|
|
2320
2551
|
console.log(` ${theme.dim('Build:')} ${theme.error(`${failBuildLabel} build failed`)}`);
|
|
2321
2552
|
}
|
|
2322
2553
|
|
|
2554
|
+
// v2.5.3: For STUCK failures, surface gate blockers so user knows what to fix.
|
|
2555
|
+
// Try refreshed state first (from result), fall back to pre-resume state.
|
|
2556
|
+
const stuckPipeline = result.state?.pipeline ?? status.state.pipeline;
|
|
2557
|
+
if (stuckPipeline?.pipelinePhase === 'STUCK') {
|
|
2558
|
+
const stuckFailingGates = Object.entries(stuckPipeline.gateResults)
|
|
2559
|
+
.filter(([, gr]) => !gr.pass)
|
|
2560
|
+
.filter(([, gr]) => gr.blockers.length > 0 || gr.missingArtifacts.length > 0 || gr.failedChecks.length > 0);
|
|
2561
|
+
if (stuckFailingGates.length > 0) {
|
|
2562
|
+
console.log();
|
|
2563
|
+
console.log(theme.warning(' Blockers:'));
|
|
2564
|
+
for (const [phase, gr] of stuckFailingGates) {
|
|
2565
|
+
const firstIssue = gr.blockers[0] ?? `Missing: ${gr.missingArtifacts[0] ?? gr.failedChecks[0] ?? 'unknown'}`;
|
|
2566
|
+
console.log(` ${theme.dim('-')} [${phase}] ${firstIssue}`);
|
|
2567
|
+
}
|
|
2568
|
+
}
|
|
2569
|
+
}
|
|
2570
|
+
|
|
2323
2571
|
printInfo('You can run /resume again with additional guidance');
|
|
2324
2572
|
}
|
|
2573
|
+
// v2.5.4: Recreate readline after workflow run (stdin corruption fix)
|
|
2574
|
+
state.refreshReadline?.();
|
|
2325
2575
|
return;
|
|
2326
2576
|
}
|
|
2327
2577
|
|
|
@@ -2484,10 +2734,16 @@ async function handleResume(state: SessionState, args: string[]): Promise<void>
|
|
|
2484
2734
|
|
|
2485
2735
|
printSuccess('Workflow completed!');
|
|
2486
2736
|
console.log(` ${theme.dim('Location:')} ${state.projectDir}`);
|
|
2737
|
+
if (state.projectDir) {
|
|
2738
|
+
printNextSteps(spec.language, state.projectDir);
|
|
2739
|
+
}
|
|
2487
2740
|
} else {
|
|
2488
2741
|
printError(result.error || 'Workflow failed');
|
|
2489
2742
|
printInfo('You can run /resume again with additional guidance');
|
|
2490
2743
|
}
|
|
2744
|
+
|
|
2745
|
+
// v2.5.4: Recreate readline after workflow run (stdin corruption fix)
|
|
2746
|
+
state.refreshReadline?.();
|
|
2491
2747
|
}
|
|
2492
2748
|
|
|
2493
2749
|
/**
|
|
@@ -2877,7 +3133,12 @@ async function handleIdea(idea: string, state: SessionState): Promise<void> {
|
|
|
2877
3133
|
state.projectDir = projectDir;
|
|
2878
3134
|
} else {
|
|
2879
3135
|
printError(workflowResult.error || 'Workflow failed');
|
|
3136
|
+
state.projectDir = projectDir; // v2.5.4: Project exists, track it for /resume
|
|
3137
|
+
printInfo('Run /resume with guidance to continue.');
|
|
2880
3138
|
}
|
|
3139
|
+
|
|
3140
|
+
// v2.5.4: Recreate readline after workflow run (stdin corruption fix)
|
|
3141
|
+
state.refreshReadline?.();
|
|
2881
3142
|
}
|
|
2882
3143
|
|
|
2883
3144
|
/**
|
|
@@ -2993,7 +3254,12 @@ async function handleNewProject(idea: string, state: SessionState): Promise<void
|
|
|
2993
3254
|
state.projectDir = projectDir;
|
|
2994
3255
|
} else {
|
|
2995
3256
|
printError(workflowResult.error || 'Workflow failed');
|
|
3257
|
+
state.projectDir = projectDir; // v2.5.4: Project exists, track it for /resume
|
|
3258
|
+
printInfo('Run /resume with guidance to continue.');
|
|
2996
3259
|
}
|
|
3260
|
+
|
|
3261
|
+
// v2.5.4: Recreate readline after workflow run (stdin corruption fix)
|
|
3262
|
+
state.refreshReadline?.();
|
|
2997
3263
|
}
|
|
2998
3264
|
|
|
2999
3265
|
/**
|
|
@@ -3048,28 +3314,76 @@ export async function startInteractiveMode(): Promise<void> {
|
|
|
3048
3314
|
|
|
3049
3315
|
console.log();
|
|
3050
3316
|
|
|
3051
|
-
//
|
|
3052
|
-
|
|
3053
|
-
|
|
3054
|
-
|
|
3055
|
-
|
|
3317
|
+
// v2.5.4: Mutable readline — recreated after workflow runs to recover from
|
|
3318
|
+
// stdin corruption caused by the Claude SDK's query() function.
|
|
3319
|
+
// See src/cli/commands/debug.ts:378-379 for documentation of the issue.
|
|
3320
|
+
let intentionalExit = false;
|
|
3321
|
+
let isPrompting = false;
|
|
3322
|
+
|
|
3323
|
+
function createReadlineInterface(): readline.Interface {
|
|
3324
|
+
const iface = readline.createInterface({
|
|
3325
|
+
input: process.stdin,
|
|
3326
|
+
output: process.stdout,
|
|
3327
|
+
});
|
|
3328
|
+
// Last-resort safety net: if stdin closes unexpectedly, exit cleanly or recreate
|
|
3329
|
+
iface.on('close', () => {
|
|
3330
|
+
if (intentionalExit) return;
|
|
3331
|
+
// Only attempt to recreate if stdin is still usable (TTY and not destroyed)
|
|
3332
|
+
if (process.stdin.isTTY && !process.stdin.destroyed && !isPrompting) {
|
|
3333
|
+
isPrompting = false;
|
|
3334
|
+
rl = createReadlineInterface();
|
|
3335
|
+
console.log();
|
|
3336
|
+
printWarning('Input stream interrupted. Restoring prompt...');
|
|
3337
|
+
promptUser();
|
|
3338
|
+
} else {
|
|
3339
|
+
// stdin is gone (piped input finished, non-TTY, etc.) — exit cleanly
|
|
3340
|
+
console.log();
|
|
3341
|
+
printInfo('Input stream closed (stdin EOF). Re-run in an interactive terminal if this was unexpected.');
|
|
3342
|
+
process.exit(1);
|
|
3343
|
+
}
|
|
3344
|
+
});
|
|
3345
|
+
return iface;
|
|
3346
|
+
}
|
|
3347
|
+
|
|
3348
|
+
function refreshReadline(): void {
|
|
3349
|
+
try { rl.close(); } catch { /* already closed */ }
|
|
3350
|
+
rl = createReadlineInterface();
|
|
3351
|
+
}
|
|
3352
|
+
|
|
3353
|
+
let rl = createReadlineInterface();
|
|
3354
|
+
|
|
3355
|
+
// Expose refreshReadline on state so handleIdea/handleResume/handleNewProject can call it
|
|
3356
|
+
state.refreshReadline = refreshReadline;
|
|
3056
3357
|
|
|
3057
3358
|
// Input loop
|
|
3058
3359
|
const promptUser = (): void => {
|
|
3360
|
+
if (isPrompting) return; // re-entrancy guard
|
|
3361
|
+
isPrompting = true;
|
|
3059
3362
|
drawInputBoxTop(state);
|
|
3060
3363
|
|
|
3061
3364
|
rl.question(getPrompt(), async (input) => {
|
|
3062
3365
|
// Draw bottom of input box after user presses enter
|
|
3063
3366
|
drawInputBoxBottom();
|
|
3064
3367
|
|
|
3065
|
-
|
|
3368
|
+
try {
|
|
3369
|
+
const shouldContinue = await handleInput(input, state);
|
|
3066
3370
|
|
|
3067
|
-
|
|
3371
|
+
if (shouldContinue) {
|
|
3372
|
+
isPrompting = false;
|
|
3373
|
+
console.log();
|
|
3374
|
+
promptUser();
|
|
3375
|
+
} else {
|
|
3376
|
+
intentionalExit = true;
|
|
3377
|
+
rl.close();
|
|
3378
|
+
process.exit(0);
|
|
3379
|
+
}
|
|
3380
|
+
} catch (err) {
|
|
3381
|
+
isPrompting = false;
|
|
3382
|
+
printError(`Unexpected error: ${err instanceof Error ? err.message : String(err)}`);
|
|
3383
|
+
printInfo('Returning to prompt. Your progress has been saved.');
|
|
3068
3384
|
console.log();
|
|
3385
|
+
refreshReadline();
|
|
3069
3386
|
promptUser();
|
|
3070
|
-
} else {
|
|
3071
|
-
rl.close();
|
|
3072
|
-
process.exit(0);
|
|
3073
3387
|
}
|
|
3074
3388
|
});
|
|
3075
3389
|
};
|