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
|
@@ -1,26 +1,45 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* RECOVERY_LOOP phase — self-heal using RCA, not guesswork.
|
|
3
3
|
* Routes via requires_phase_rewind_to (P1-3). Max 5 iterations.
|
|
4
|
+
* v2.1: Extracts reviewer feedback from consensus failures and
|
|
5
|
+
* builds structured revision directive for sessionGuidance.
|
|
4
6
|
*/
|
|
5
7
|
|
|
6
|
-
import
|
|
8
|
+
import { existsSync, unlinkSync } from 'node:fs';
|
|
9
|
+
import { join } from 'node:path';
|
|
10
|
+
|
|
11
|
+
import type { PipelinePhase, ConsensusPacket, GateCheckResult } from '../types.js';
|
|
7
12
|
import type { PhaseContext, PhaseResult } from './phase-context.js';
|
|
8
13
|
import { successResult, failureResult, triggerJournalist } from './phase-context.js';
|
|
9
14
|
import { buildRCAPacket } from '../packets/rca-packet-builder.js';
|
|
10
15
|
|
|
11
16
|
export async function runRecoveryLoop(context: PhaseContext): Promise<PhaseResult> {
|
|
12
|
-
const { pipeline, artifactManager, skillLoader } = context;
|
|
17
|
+
const { pipeline, artifactManager, skillLoader, skillUsageRegistry } = context;
|
|
13
18
|
const artifacts = [];
|
|
14
19
|
|
|
15
20
|
try {
|
|
16
|
-
// 1. Load debugger skill
|
|
17
|
-
const debuggerSkill = skillLoader.
|
|
21
|
+
// 1. Load debugger skill with metadata
|
|
22
|
+
const { definition: debuggerSkill, meta: debuggerMeta } = skillLoader.loadSkillWithMeta('DEBUGGER');
|
|
18
23
|
|
|
19
24
|
// 2. Gather failure evidence
|
|
20
25
|
const failedPhase = pipeline.failedPhase;
|
|
21
26
|
const failedGateResult = failedPhase ? pipeline.gateResults[failedPhase] : undefined;
|
|
22
27
|
const failedChecks = failedPhase ? pipeline.gateChecks[failedPhase] ?? [] : [];
|
|
23
28
|
|
|
29
|
+
// 2a. Detect missing module errors in stderr — invalidate install marker
|
|
30
|
+
const combinedStderr = failedChecks
|
|
31
|
+
.filter((c) => c.status === 'fail')
|
|
32
|
+
.map((c) => c.stderr_summary ?? '')
|
|
33
|
+
.join('\n');
|
|
34
|
+
const missingModule = /Cannot find module|ModuleNotFoundError|Failed to resolve import/
|
|
35
|
+
.test(combinedStderr);
|
|
36
|
+
if (missingModule) {
|
|
37
|
+
try {
|
|
38
|
+
const markerPath = join(context.projectDir, '.popeye', 'install-marker.json');
|
|
39
|
+
if (existsSync(markerPath)) unlinkSync(markerPath);
|
|
40
|
+
} catch { /* non-fatal */ }
|
|
41
|
+
}
|
|
42
|
+
|
|
24
43
|
const failureEvidence = [
|
|
25
44
|
`Failed phase: ${failedPhase ?? 'unknown'}`,
|
|
26
45
|
failedGateResult
|
|
@@ -31,6 +50,50 @@ export async function runRecoveryLoop(context: PhaseContext): Promise<PhaseResul
|
|
|
31
50
|
: 'No check failures',
|
|
32
51
|
].join('\n');
|
|
33
52
|
|
|
53
|
+
// 2b. For consensus failures, build revision directive from reviewer feedback
|
|
54
|
+
if (failedPhase?.startsWith('CONSENSUS_')) {
|
|
55
|
+
const directive = buildRevisionDirective(pipeline, failedPhase);
|
|
56
|
+
if (directive) {
|
|
57
|
+
const existing = pipeline.sessionGuidance ?? '';
|
|
58
|
+
const marker = '--- REVISION DIRECTIVE ---';
|
|
59
|
+
const base = existing.includes(marker)
|
|
60
|
+
? existing.slice(0, existing.indexOf(marker)).trim()
|
|
61
|
+
: existing;
|
|
62
|
+
pipeline.sessionGuidance = [base, '', marker, directive.slice(0, 3000)].join('\n').trim();
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// 2c. For QA/build failures, build a targeted fix directive from test stderr
|
|
67
|
+
// so the implementation phase knows exactly what to fix on rewind.
|
|
68
|
+
if (failedPhase === 'QA_VALIDATION' || failedPhase === 'PRODUCTION_GATE'
|
|
69
|
+
|| failedPhase === 'IMPLEMENTATION') {
|
|
70
|
+
const failedCheckDetails = failedChecks
|
|
71
|
+
.filter((c) => c.status === 'fail')
|
|
72
|
+
.map((c) => [
|
|
73
|
+
`**${c.check_type}** (exit code ${c.exit_code}):`,
|
|
74
|
+
`Command: \`${c.command}\``,
|
|
75
|
+
c.stderr_summary ? c.stderr_summary.slice(0, 500) : 'No stderr captured',
|
|
76
|
+
].join('\n'))
|
|
77
|
+
.join('\n\n');
|
|
78
|
+
|
|
79
|
+
if (failedCheckDetails) {
|
|
80
|
+
const existing = pipeline.sessionGuidance ?? '';
|
|
81
|
+
const marker = '--- QA FIX DIRECTIVE ---';
|
|
82
|
+
const base = existing.includes(marker)
|
|
83
|
+
? existing.slice(0, existing.indexOf(marker)).trim()
|
|
84
|
+
: existing;
|
|
85
|
+
const directive = [
|
|
86
|
+
`Fix the following failures (recovery iteration ${pipeline.recoveryCount}):`,
|
|
87
|
+
'',
|
|
88
|
+
failedCheckDetails,
|
|
89
|
+
'',
|
|
90
|
+
'Apply targeted fixes only. Do not rewrite code that already works.',
|
|
91
|
+
].join('\n');
|
|
92
|
+
pipeline.sessionGuidance = [base, '', marker, directive.slice(0, 3000)]
|
|
93
|
+
.join('\n').trim();
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
34
97
|
// 3. Generate RCA via Claude with Debugger skill
|
|
35
98
|
const { executePrompt } = await import('../../adapters/claude.js');
|
|
36
99
|
const guidance = pipeline.sessionGuidance;
|
|
@@ -54,6 +117,33 @@ export async function runRecoveryLoop(context: PhaseContext): Promise<PhaseResul
|
|
|
54
117
|
const rcaResult = await executePrompt(rcaPrompt);
|
|
55
118
|
const rcaResponse = rcaResult.response;
|
|
56
119
|
|
|
120
|
+
// Record skill usage — debugger skill injected into RCA prompt
|
|
121
|
+
skillUsageRegistry.record('DEBUGGER', 'RECOVERY_LOOP', 'system_prompt', debuggerMeta.source, debuggerMeta.version);
|
|
122
|
+
|
|
123
|
+
// 3b. Build recovery micro-plan from RCA + stderr
|
|
124
|
+
const microPlan = buildRecoveryMicroPlan(rcaResponse, failedChecks, pipeline.recoveryCount);
|
|
125
|
+
|
|
126
|
+
// 3c. Store micro-plan as artifact for traceability
|
|
127
|
+
const microPlanEntry = artifactManager.createAndStoreText(
|
|
128
|
+
'recovery_fix_plan',
|
|
129
|
+
microPlan.plan,
|
|
130
|
+
'RECOVERY_LOOP',
|
|
131
|
+
);
|
|
132
|
+
artifacts.push(microPlanEntry);
|
|
133
|
+
|
|
134
|
+
// 3d. Inject micro-plan into sessionGuidance (replaces any prior fix directive)
|
|
135
|
+
{
|
|
136
|
+
const existing = pipeline.sessionGuidance ?? '';
|
|
137
|
+
const markers = ['--- QA FIX DIRECTIVE ---', '--- RECOVERY FIX PLAN ---'];
|
|
138
|
+
let base = existing;
|
|
139
|
+
for (const m of markers) {
|
|
140
|
+
if (base.includes(m)) base = base.slice(0, base.indexOf(m)).trim();
|
|
141
|
+
}
|
|
142
|
+
pipeline.sessionGuidance = [
|
|
143
|
+
base, '', '--- RECOVERY FIX PLAN ---', microPlan.plan.slice(0, 3000),
|
|
144
|
+
].join('\n').trim();
|
|
145
|
+
}
|
|
146
|
+
|
|
57
147
|
// 4. Build RCA packet
|
|
58
148
|
const rcaPacket = buildRCAPacket({
|
|
59
149
|
incidentSummary: `Gate failure at ${failedPhase ?? 'unknown'} (recovery iteration ${pipeline.recoveryCount})`,
|
|
@@ -103,18 +193,150 @@ function determineRewindTarget(
|
|
|
103
193
|
_rcaResponse: string,
|
|
104
194
|
failedPhase: PipelinePhase | undefined,
|
|
105
195
|
): PipelinePhase | undefined {
|
|
106
|
-
// If the failure was in production gate or audit, rewind to implementation
|
|
107
196
|
if (failedPhase === 'PRODUCTION_GATE' || failedPhase === 'AUDIT') {
|
|
108
197
|
return 'IMPLEMENTATION';
|
|
109
198
|
}
|
|
110
|
-
// If in QA, rewind to implementation
|
|
111
199
|
if (failedPhase === 'QA_VALIDATION') {
|
|
112
200
|
return 'IMPLEMENTATION';
|
|
113
201
|
}
|
|
114
|
-
// For consensus failures, rewind to the phase being validated
|
|
115
202
|
if (failedPhase === 'CONSENSUS_MASTER_PLAN') return 'INTAKE';
|
|
116
203
|
if (failedPhase === 'CONSENSUS_ARCHITECTURE') return 'ARCHITECTURE';
|
|
117
204
|
if (failedPhase === 'CONSENSUS_ROLE_PLANS') return 'ROLE_PLANNING';
|
|
118
205
|
|
|
119
206
|
return undefined;
|
|
120
207
|
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Extract reviewer feedback from the latest consensus artifact and
|
|
211
|
+
* build a structured revision directive for the planner.
|
|
212
|
+
*
|
|
213
|
+
* Uses Set<string> for dedup. Output is capped at 3000 chars by caller.
|
|
214
|
+
*/
|
|
215
|
+
function buildRevisionDirective(
|
|
216
|
+
pipeline: { artifacts: Array<{ type: string; content?: unknown }> },
|
|
217
|
+
failedPhase: string,
|
|
218
|
+
): string | null {
|
|
219
|
+
// Find the latest consensus artifact for this phase
|
|
220
|
+
const consensusArtifacts = pipeline.artifacts.filter(
|
|
221
|
+
(a) => a.type === 'consensus' && a.content,
|
|
222
|
+
);
|
|
223
|
+
if (consensusArtifacts.length === 0) return null;
|
|
224
|
+
|
|
225
|
+
const latest = consensusArtifacts[consensusArtifacts.length - 1];
|
|
226
|
+
const packet = latest.content as ConsensusPacket | undefined;
|
|
227
|
+
if (!packet?.reviewer_votes) return null;
|
|
228
|
+
|
|
229
|
+
const blockers = new Set<string>();
|
|
230
|
+
const required = new Set<string>();
|
|
231
|
+
const suggestions = new Set<string>();
|
|
232
|
+
|
|
233
|
+
for (const vote of packet.reviewer_votes) {
|
|
234
|
+
for (const issue of vote.blocking_issues) {
|
|
235
|
+
const trimmed = issue.trim();
|
|
236
|
+
if (trimmed) blockers.add(trimmed);
|
|
237
|
+
}
|
|
238
|
+
for (const change of (vote.required_changes ?? [])) {
|
|
239
|
+
const trimmed = change.trim();
|
|
240
|
+
if (trimmed) required.add(trimmed);
|
|
241
|
+
}
|
|
242
|
+
for (const suggestion of vote.suggestions) {
|
|
243
|
+
const trimmed = suggestion.trim();
|
|
244
|
+
if (trimmed) suggestions.add(trimmed);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
if (blockers.size === 0 && required.size === 0 && suggestions.size === 0) return null;
|
|
249
|
+
|
|
250
|
+
const lines: string[] = [
|
|
251
|
+
`Revise the plan to address reviewer feedback from ${failedPhase}:`,
|
|
252
|
+
'',
|
|
253
|
+
];
|
|
254
|
+
|
|
255
|
+
if (blockers.size > 0) {
|
|
256
|
+
lines.push('BLOCKING (must fix):');
|
|
257
|
+
for (const b of blockers) lines.push(`- ${b}`);
|
|
258
|
+
lines.push('');
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
if (required.size > 0) {
|
|
262
|
+
lines.push('REQUIRED CHANGES:');
|
|
263
|
+
for (const r of required) lines.push(`- ${r}`);
|
|
264
|
+
lines.push('');
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
if (suggestions.size > 0) {
|
|
268
|
+
lines.push('SUGGESTIONS:');
|
|
269
|
+
for (const s of suggestions) lines.push(`- ${s}`);
|
|
270
|
+
lines.push('');
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
lines.push('Keep existing plan structure. Apply targeted revisions only.');
|
|
274
|
+
|
|
275
|
+
return lines.join('\n');
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* Build a structured micro-fix plan from RCA + test failures.
|
|
280
|
+
* Returns the plan text and a risk assessment for consensus gating.
|
|
281
|
+
*
|
|
282
|
+
* Heuristics:
|
|
283
|
+
* - Low risk (skip consensus): <=3 files, no schema/dep/config changes
|
|
284
|
+
* - Medium risk (consensus): schema, dependency, or build config changes
|
|
285
|
+
* - High risk (consensus): API changes, >5 files
|
|
286
|
+
*/
|
|
287
|
+
function buildRecoveryMicroPlan(
|
|
288
|
+
rcaResponse: string,
|
|
289
|
+
failedChecks: GateCheckResult[],
|
|
290
|
+
recoveryCount: number,
|
|
291
|
+
): { plan: string; needsConsensus: boolean; riskLevel: 'low' | 'medium' | 'high' } {
|
|
292
|
+
const stderrLines = failedChecks
|
|
293
|
+
.filter((c) => c.status === 'fail')
|
|
294
|
+
.map((c) => c.stderr_summary ?? '')
|
|
295
|
+
.join('\n');
|
|
296
|
+
|
|
297
|
+
// Combine RCA + stderr for signal extraction
|
|
298
|
+
const combined = rcaResponse + '\n' + stderrLines;
|
|
299
|
+
const combinedLower = combined.toLowerCase();
|
|
300
|
+
|
|
301
|
+
const touchesSchema = /schema|migration|prisma|drizzle|typeorm|knex/.test(combinedLower);
|
|
302
|
+
const touchesDeps = /package\.json|requirements\.txt|dependency|npm install|pip install/
|
|
303
|
+
.test(combinedLower);
|
|
304
|
+
const touchesConfig =
|
|
305
|
+
/tsconfig\.json|vite\.config|jest\.config|vitest\.config|docker-compose|\.github\/workflows/
|
|
306
|
+
.test(combinedLower);
|
|
307
|
+
const touchesApi = /api route|endpoint|public api|breaking change/.test(combinedLower);
|
|
308
|
+
|
|
309
|
+
// Extract file paths from BOTH RCA and stderr
|
|
310
|
+
const mentionedFiles = combined.match(/[\w/.-]+\.(ts|tsx|js|jsx|py|sql|prisma)/gi) ?? [];
|
|
311
|
+
const uniqueFiles = [...new Set(mentionedFiles)];
|
|
312
|
+
|
|
313
|
+
let riskLevel: 'low' | 'medium' | 'high' = 'low';
|
|
314
|
+
if (touchesApi || uniqueFiles.length > 5) riskLevel = 'high';
|
|
315
|
+
else if (touchesSchema || touchesDeps || touchesConfig) riskLevel = 'medium';
|
|
316
|
+
|
|
317
|
+
// Only require consensus for medium+ risk on second+ attempt
|
|
318
|
+
const needsConsensus = riskLevel !== 'low' && recoveryCount >= 2;
|
|
319
|
+
|
|
320
|
+
const plan = [
|
|
321
|
+
`# Recovery Fix Plan (iteration ${recoveryCount})`,
|
|
322
|
+
'',
|
|
323
|
+
`**Risk level:** ${riskLevel}`,
|
|
324
|
+
`**Files likely affected:** ${uniqueFiles.length > 0 ? uniqueFiles.join(', ') : 'unknown'}`,
|
|
325
|
+
`**Consensus required:** ${needsConsensus ? 'yes' : 'no'}`,
|
|
326
|
+
'',
|
|
327
|
+
'## Root Cause Summary',
|
|
328
|
+
rcaResponse.slice(0, 800),
|
|
329
|
+
'',
|
|
330
|
+
'## Test Failures',
|
|
331
|
+
stderrLines.slice(0, 1000) || 'No stderr captured',
|
|
332
|
+
'',
|
|
333
|
+
'## Fix Checklist',
|
|
334
|
+
'- [ ] Address root cause identified above',
|
|
335
|
+
'- [ ] Fix failing test assertions',
|
|
336
|
+
'- [ ] Verify no regressions in passing tests',
|
|
337
|
+
touchesSchema ? '- [ ] Review schema/migration changes for correctness' : '',
|
|
338
|
+
touchesDeps ? '- [ ] Verify dependency changes are necessary and compatible' : '',
|
|
339
|
+
].filter(Boolean).join('\n');
|
|
340
|
+
|
|
341
|
+
return { plan, needsConsensus, riskLevel };
|
|
342
|
+
}
|
|
@@ -8,12 +8,12 @@ import type { PhaseContext, PhaseResult } from './phase-context.js';
|
|
|
8
8
|
import { successResult, failureResult } from './phase-context.js';
|
|
9
9
|
import { generateRepoSnapshot, createSnapshotArtifact, diffSnapshots } from '../repo-snapshot.js';
|
|
10
10
|
import type { RepoSnapshot, ChangeRequest } from '../types.js';
|
|
11
|
-
import { buildChangeRequest, formatChangeRequest, routeChangeRequest } from '../change-request.js';
|
|
11
|
+
import { buildChangeRequest, formatChangeRequest, routeChangeRequest, computeDriftKey, isDuplicateCR } from '../change-request.js';
|
|
12
12
|
import { existsSync, readFileSync } from 'node:fs';
|
|
13
13
|
import { join } from 'node:path';
|
|
14
14
|
|
|
15
15
|
export async function runReview(context: PhaseContext): Promise<PhaseResult> {
|
|
16
|
-
const { pipeline, artifactManager, projectDir } = context;
|
|
16
|
+
const { pipeline, artifactManager, skillLoader, skillUsageRegistry, projectDir } = context;
|
|
17
17
|
const artifacts = [];
|
|
18
18
|
const changeRequests: ChangeRequest[] = [];
|
|
19
19
|
|
|
@@ -24,11 +24,18 @@ export async function runReview(context: PhaseContext): Promise<PhaseResult> {
|
|
|
24
24
|
artifacts.push(snapshotEntry);
|
|
25
25
|
pipeline.latestRepoSnapshot = artifactManager.toArtifactRef(snapshotEntry);
|
|
26
26
|
|
|
27
|
-
// 2. Find
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
27
|
+
// 2. Find baseline snapshot for drift detection
|
|
28
|
+
// v2.4.9: Check baselineSnapshotOverride first (set after config CR resolved)
|
|
29
|
+
let baselineSnapshot = pipeline.baselineSnapshotOverride
|
|
30
|
+
? pipeline.artifacts.find((a) => a.id === pipeline.baselineSnapshotOverride!.artifact_id)
|
|
31
|
+
: undefined;
|
|
32
|
+
// Fallback to CONSENSUS_ROLE_PLANS snapshot
|
|
33
|
+
if (!baselineSnapshot) {
|
|
34
|
+
const rolePlanSnapshots = pipeline.artifacts.filter(
|
|
35
|
+
(a) => a.type === 'repo_snapshot' && a.phase === 'CONSENSUS_ROLE_PLANS',
|
|
36
|
+
);
|
|
37
|
+
baselineSnapshot = rolePlanSnapshots[rolePlanSnapshots.length - 1];
|
|
38
|
+
}
|
|
32
39
|
|
|
33
40
|
let driftReport = 'No baseline snapshot found for drift detection.';
|
|
34
41
|
let hasDrift = false;
|
|
@@ -53,32 +60,62 @@ export async function runReview(context: PhaseContext): Promise<PhaseResult> {
|
|
|
53
60
|
].filter(Boolean).join('\n');
|
|
54
61
|
|
|
55
62
|
// v1.1: Create change requests for detected drift
|
|
63
|
+
// v2.4.9: Deduplicate — skip if an active CR with the same drift_key exists
|
|
56
64
|
if (diff.changed_configs.length > 0) {
|
|
57
|
-
const
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
description: `Config files changed during implementation: ${diff.changed_configs.join(', ')}`,
|
|
62
|
-
justification: 'Detected by snapshot diff during review phase',
|
|
63
|
-
affectedArtifacts: [artifactManager.toArtifactRef(snapshotEntry)],
|
|
64
|
-
affectedPhases: ['IMPLEMENTATION', 'QA_VALIDATION'],
|
|
65
|
-
riskLevel: diff.changed_configs.length > 3 ? 'high' : 'medium',
|
|
65
|
+
const configHashPairs = diff.changed_configs.map((cfgPath) => {
|
|
66
|
+
const before = baselineData.config_files?.find((f) => f.path === cfgPath)?.content_hash ?? 'unknown';
|
|
67
|
+
const after = currentSnapshot.config_files?.find((f) => f.path === cfgPath)?.content_hash ?? 'unknown';
|
|
68
|
+
return `${cfgPath}:${before}->${after}`;
|
|
66
69
|
});
|
|
67
|
-
|
|
70
|
+
const configDriftKey = computeDriftKey(
|
|
71
|
+
'config',
|
|
72
|
+
baselineSnapshot!.id,
|
|
73
|
+
diff.changed_configs,
|
|
74
|
+
configHashPairs,
|
|
75
|
+
);
|
|
76
|
+
|
|
77
|
+
if (!isDuplicateCR(pipeline.pendingChangeRequests, configDriftKey)) {
|
|
78
|
+
const cr = buildChangeRequest({
|
|
79
|
+
originPhase: 'REVIEW',
|
|
80
|
+
requestedBy: 'REVIEWER',
|
|
81
|
+
changeType: 'config',
|
|
82
|
+
description: `Config files changed during implementation: ${diff.changed_configs.join(', ')}`,
|
|
83
|
+
justification: 'Detected by snapshot diff during review phase',
|
|
84
|
+
affectedArtifacts: [artifactManager.toArtifactRef(snapshotEntry)],
|
|
85
|
+
affectedPhases: ['IMPLEMENTATION', 'QA_VALIDATION'],
|
|
86
|
+
riskLevel: diff.changed_configs.length > 3 ? 'high' : 'medium',
|
|
87
|
+
driftKey: configDriftKey,
|
|
88
|
+
});
|
|
89
|
+
changeRequests.push(cr);
|
|
90
|
+
}
|
|
68
91
|
}
|
|
69
92
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
93
|
+
// v2.5.1: Only check scope drift on revision-over-revision passes.
|
|
94
|
+
// On first pass, baseline = CONSENSUS_ROLE_PLANS (pre-implementation),
|
|
95
|
+
// so large line deltas are expected from implementation — not scope drift.
|
|
96
|
+
const isRevisionComparison = !!pipeline.baselineSnapshotOverride;
|
|
97
|
+
if (isRevisionComparison && Math.abs(diff.lines_delta) > 1000) {
|
|
98
|
+
const scopeDriftKey = computeDriftKey(
|
|
99
|
+
'scope',
|
|
100
|
+
baselineSnapshot!.id,
|
|
101
|
+
[`lines_delta:${diff.lines_delta}`],
|
|
102
|
+
[],
|
|
103
|
+
);
|
|
104
|
+
|
|
105
|
+
if (!isDuplicateCR(pipeline.pendingChangeRequests, scopeDriftKey)) {
|
|
106
|
+
const cr = buildChangeRequest({
|
|
107
|
+
originPhase: 'REVIEW',
|
|
108
|
+
requestedBy: 'REVIEWER',
|
|
109
|
+
changeType: 'scope',
|
|
110
|
+
description: `Significant scope drift detected: ${diff.lines_delta > 0 ? '+' : ''}${diff.lines_delta} lines`,
|
|
111
|
+
justification: 'Large line delta suggests scope changes beyond approved plans',
|
|
112
|
+
affectedArtifacts: [artifactManager.toArtifactRef(snapshotEntry)],
|
|
113
|
+
affectedPhases: ['CONSENSUS_MASTER_PLAN', 'IMPLEMENTATION'],
|
|
114
|
+
riskLevel: 'high',
|
|
115
|
+
driftKey: scopeDriftKey,
|
|
116
|
+
});
|
|
117
|
+
changeRequests.push(cr);
|
|
118
|
+
}
|
|
82
119
|
}
|
|
83
120
|
} else {
|
|
84
121
|
driftReport = 'No drift detected between approved plans and implementation.';
|
|
@@ -107,10 +144,15 @@ export async function runReview(context: PhaseContext): Promise<PhaseResult> {
|
|
|
107
144
|
change_type: cr.change_type,
|
|
108
145
|
target_phase: routeChangeRequest(cr),
|
|
109
146
|
status: 'proposed',
|
|
147
|
+
drift_key: cr.drift_key,
|
|
110
148
|
});
|
|
111
149
|
}
|
|
112
150
|
|
|
113
|
-
//
|
|
151
|
+
// 3b. Load REVIEWER skill and record usage for skill-guided review
|
|
152
|
+
const { definition: reviewerSkill, meta: reviewerMeta } = skillLoader.loadSkillWithMeta('REVIEWER');
|
|
153
|
+
skillUsageRegistry.record('REVIEWER', 'REVIEW', 'review_prompt', reviewerMeta.source, reviewerMeta.version);
|
|
154
|
+
|
|
155
|
+
// 4. Create review decision artifact (skill-guided)
|
|
114
156
|
const reviewDoc = [
|
|
115
157
|
'# Review Decision',
|
|
116
158
|
'',
|
|
@@ -125,6 +167,7 @@ export async function runReview(context: PhaseContext): Promise<PhaseResult> {
|
|
|
125
167
|
changeRequests.length > 0 ? '## Change Requests\n' + changeRequests.map((cr) => `- ${cr.cr_id}: ${cr.description}`).join('\n') : '',
|
|
126
168
|
'',
|
|
127
169
|
'## Plan Alignment',
|
|
170
|
+
`Reviewed using ${reviewerSkill.role} skill (v${reviewerSkill.version}).`,
|
|
128
171
|
'Implementation reviewed against approved role plans.',
|
|
129
172
|
'',
|
|
130
173
|
'## Decision',
|
|
@@ -9,6 +9,7 @@ import { join } from 'node:path';
|
|
|
9
9
|
import type { PipelineRole } from '../types.js';
|
|
10
10
|
import type { PhaseContext, PhaseResult } from './phase-context.js';
|
|
11
11
|
import { successResult, failureResult } from './phase-context.js';
|
|
12
|
+
import { STRATEGY_ROLES, loadStrategyForRole } from '../strategy-context.js';
|
|
12
13
|
|
|
13
14
|
/** Roles that produce implementation plans */
|
|
14
15
|
const PLANNING_ROLES: PipelineRole[] = [
|
|
@@ -16,11 +17,14 @@ const PLANNING_ROLES: PipelineRole[] = [
|
|
|
16
17
|
'BACKEND_PROGRAMMER',
|
|
17
18
|
'FRONTEND_PROGRAMMER',
|
|
18
19
|
'WEBSITE_PROGRAMMER',
|
|
20
|
+
'UI_UX_SPECIALIST',
|
|
21
|
+
'MARKETING_EXPERT',
|
|
22
|
+
'SOCIAL_EXPERT',
|
|
19
23
|
'QA_TESTER',
|
|
20
24
|
];
|
|
21
25
|
|
|
22
26
|
export async function runRolePlanning(context: PhaseContext): Promise<PhaseResult> {
|
|
23
|
-
const { pipeline, artifactManager, skillLoader, projectDir } = context;
|
|
27
|
+
const { pipeline, artifactManager, skillLoader, skillUsageRegistry, projectDir } = context;
|
|
24
28
|
const artifacts = [];
|
|
25
29
|
|
|
26
30
|
try {
|
|
@@ -46,13 +50,16 @@ export async function runRolePlanning(context: PhaseContext): Promise<PhaseResul
|
|
|
46
50
|
|
|
47
51
|
const { executePrompt } = await import('../../adapters/claude.js');
|
|
48
52
|
|
|
53
|
+
// Load strategy once for all roles
|
|
54
|
+
const strategyBlock = loadStrategyForRole(projectDir);
|
|
55
|
+
|
|
49
56
|
// Generate plan for each role (skip roles not in activeRoles)
|
|
50
57
|
for (const role of PLANNING_ROLES) {
|
|
51
58
|
if (!pipeline.activeRoles.includes(role)) {
|
|
52
59
|
continue;
|
|
53
60
|
}
|
|
54
61
|
|
|
55
|
-
const skill = skillLoader.
|
|
62
|
+
const { definition: skill, meta } = skillLoader.loadSkillWithMeta(role);
|
|
56
63
|
|
|
57
64
|
const planPrompt = [
|
|
58
65
|
skill.systemPrompt,
|
|
@@ -63,6 +70,10 @@ export async function runRolePlanning(context: PhaseContext): Promise<PhaseResul
|
|
|
63
70
|
'## Architecture',
|
|
64
71
|
architectureContent.slice(0, 5000),
|
|
65
72
|
'',
|
|
73
|
+
// Conditionally inject strategy for website/marketing/social roles
|
|
74
|
+
...(strategyBlock && STRATEGY_ROLES.includes(role)
|
|
75
|
+
? ['## Website Marketing Strategy (authoritative reference)', strategyBlock, '']
|
|
76
|
+
: []),
|
|
66
77
|
'## Instructions',
|
|
67
78
|
`Create your ${role} implementation plan. Include:`,
|
|
68
79
|
'- Deterministic file-level outputs',
|
|
@@ -74,6 +85,14 @@ export async function runRolePlanning(context: PhaseContext): Promise<PhaseResul
|
|
|
74
85
|
const planResult = await executePrompt(planPrompt);
|
|
75
86
|
const plan = planResult.response;
|
|
76
87
|
|
|
88
|
+
// Record skill usage — role skill injected into planning prompt
|
|
89
|
+
skillUsageRegistry.record(role, 'ROLE_PLANNING', 'planning_prompt', meta.source, meta.version);
|
|
90
|
+
|
|
91
|
+
// Record strategy context usage when injected
|
|
92
|
+
if (strategyBlock && STRATEGY_ROLES.includes(role)) {
|
|
93
|
+
skillUsageRegistry.record(role, 'ROLE_PLANNING', 'strategy_context', 'disk', '1');
|
|
94
|
+
}
|
|
95
|
+
|
|
77
96
|
const entry = artifactManager.createAndStoreText(
|
|
78
97
|
'role_plan',
|
|
79
98
|
`# ${role} Plan\n\n${plan}`,
|
|
@@ -36,6 +36,16 @@ export async function runStuck(context: PhaseContext): Promise<PhaseResult> {
|
|
|
36
36
|
'- Determine if scope changes are needed',
|
|
37
37
|
'- Decide whether to restart pipeline from a specific phase',
|
|
38
38
|
'',
|
|
39
|
+
// v2.6.0: Auto-recovery attempt details
|
|
40
|
+
...(pipeline.autoRecoveryResult ? [
|
|
41
|
+
'## Auto-Recovery Attempt',
|
|
42
|
+
`Result: ${pipeline.autoRecoveryResult}`,
|
|
43
|
+
(() => {
|
|
44
|
+
const autoArtifact = pipeline.artifacts.find(a => a.type === 'auto_recovery_guidance');
|
|
45
|
+
return autoArtifact ? `Guidance: ${autoArtifact.path}` : 'No guidance artifact generated.';
|
|
46
|
+
})(),
|
|
47
|
+
'',
|
|
48
|
+
] : []),
|
|
39
49
|
'## Artifacts That May Need Update',
|
|
40
50
|
...pipeline.artifacts
|
|
41
51
|
.filter((a) => a.phase === pipeline.failedPhase)
|
|
@@ -37,6 +37,8 @@ const CONFIG_FILES = new Set([
|
|
|
37
37
|
'prisma/schema.prisma', 'alembic.ini',
|
|
38
38
|
'requirements.txt', 'setup.py', 'setup.cfg',
|
|
39
39
|
'Makefile', 'Procfile',
|
|
40
|
+
'poetry.lock', 'package-lock.json', 'pnpm-lock.yaml',
|
|
41
|
+
'yarn.lock', 'pnpm-workspace.yaml',
|
|
40
42
|
]);
|
|
41
43
|
|
|
42
44
|
const CODE_EXTENSIONS = new Set([
|
|
@@ -223,6 +225,7 @@ function extractKeyFields(configName: string, content: string): Record<string, u
|
|
|
223
225
|
scripts: pkg.scripts,
|
|
224
226
|
dependencies: pkg.dependencies ? Object.keys(pkg.dependencies) : [],
|
|
225
227
|
devDependencies: pkg.devDependencies ? Object.keys(pkg.devDependencies) : [],
|
|
228
|
+
workspaces: pkg.workspaces,
|
|
226
229
|
};
|
|
227
230
|
}
|
|
228
231
|
|
|
@@ -16,6 +16,8 @@ import type {
|
|
|
16
16
|
ArtifactEntry,
|
|
17
17
|
} from './types.js';
|
|
18
18
|
import type { SkillLoader, SkillDefinition } from './skill-loader.js';
|
|
19
|
+
import type { SkillUsageRegistry } from './skills/usage-registry.js';
|
|
20
|
+
import { STRATEGY_ROLES, loadStrategyForRole } from './strategy-context.js';
|
|
19
21
|
|
|
20
22
|
// ─── Types ───────────────────────────────────────────────
|
|
21
23
|
|
|
@@ -65,8 +67,12 @@ export function buildRoleExecutionContext(
|
|
|
65
67
|
const allowedPaths = extractAllowedPaths(planContent, role);
|
|
66
68
|
const forbiddenPatterns = extractForbiddenPatterns(role);
|
|
67
69
|
|
|
68
|
-
//
|
|
69
|
-
const
|
|
70
|
+
// Load strategy for roles that need it
|
|
71
|
+
const strategyBlock = STRATEGY_ROLES.includes(role)
|
|
72
|
+
? loadStrategyForRole(projectDir) : undefined;
|
|
73
|
+
|
|
74
|
+
// Build system prompt combining skill + role constraints + optional strategy
|
|
75
|
+
const systemPrompt = buildRoleSystemPrompt(role, skill, planContent, forbiddenPatterns, strategyBlock);
|
|
70
76
|
|
|
71
77
|
return {
|
|
72
78
|
role,
|
|
@@ -114,6 +120,7 @@ export function buildAllRoleContexts(
|
|
|
114
120
|
pipeline: PipelineState,
|
|
115
121
|
skillLoader: SkillLoader,
|
|
116
122
|
projectDir: string,
|
|
123
|
+
skillUsageRegistry?: SkillUsageRegistry,
|
|
117
124
|
): Map<PipelineRole, RoleExecutionContext> {
|
|
118
125
|
const contexts = new Map<PipelineRole, RoleExecutionContext>();
|
|
119
126
|
|
|
@@ -129,8 +136,18 @@ export function buildAllRoleContexts(
|
|
|
129
136
|
const role = detectRoleFromPlan(content, pipeline.activeRoles);
|
|
130
137
|
if (!role) continue;
|
|
131
138
|
|
|
132
|
-
const skill = skillLoader.
|
|
139
|
+
const { definition: skill, meta } = skillLoader.loadSkillWithMeta(role);
|
|
133
140
|
contexts.set(role, buildRoleExecutionContext(role, skill, rolePlan, projectDir));
|
|
141
|
+
|
|
142
|
+
// Record skill usage — role skill injected into execution context
|
|
143
|
+
if (skillUsageRegistry) {
|
|
144
|
+
skillUsageRegistry.record(role, 'IMPLEMENTATION', 'role_context', meta.source, meta.version);
|
|
145
|
+
|
|
146
|
+
// Record strategy context usage for roles that receive it
|
|
147
|
+
if (STRATEGY_ROLES.includes(role) && loadStrategyForRole(projectDir)) {
|
|
148
|
+
skillUsageRegistry.record(role, 'IMPLEMENTATION', 'strategy_context', 'disk', '1');
|
|
149
|
+
}
|
|
150
|
+
}
|
|
134
151
|
}
|
|
135
152
|
|
|
136
153
|
return contexts;
|
|
@@ -189,6 +206,7 @@ function buildRoleSystemPrompt(
|
|
|
189
206
|
skill: SkillDefinition,
|
|
190
207
|
planContent: string,
|
|
191
208
|
forbiddenPatterns: string[],
|
|
209
|
+
strategyContext?: string,
|
|
192
210
|
): string {
|
|
193
211
|
const lines = [
|
|
194
212
|
`# Role: ${role}`,
|
|
@@ -199,9 +217,17 @@ function buildRoleSystemPrompt(
|
|
|
199
217
|
'## Your Approved Role Plan',
|
|
200
218
|
planContent.slice(0, 4000),
|
|
201
219
|
'',
|
|
220
|
+
];
|
|
221
|
+
|
|
222
|
+
// Inject strategy after plan, before constraints
|
|
223
|
+
if (strategyContext) {
|
|
224
|
+
lines.push('## Website Marketing Strategy', strategyContext, '');
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
lines.push(
|
|
202
228
|
'## Constraints',
|
|
203
229
|
...skill.constraints.map((c) => `- ${c}`),
|
|
204
|
-
|
|
230
|
+
);
|
|
205
231
|
|
|
206
232
|
if (forbiddenPatterns.length > 0) {
|
|
207
233
|
lines.push(
|
|
@@ -9,6 +9,7 @@ import { join } from 'node:path';
|
|
|
9
9
|
|
|
10
10
|
import type { PipelineRole } from './types.js';
|
|
11
11
|
import { DEFAULT_SKILLS } from './skills/defaults.js';
|
|
12
|
+
import type { SkillSource } from './skills/usage-registry.js';
|
|
12
13
|
|
|
13
14
|
// ─── Types ───────────────────────────────────────────────
|
|
14
15
|
|
|
@@ -21,6 +22,14 @@ export interface SkillDefinition {
|
|
|
21
22
|
depends_on?: PipelineRole[];
|
|
22
23
|
}
|
|
23
24
|
|
|
25
|
+
export interface SkillLoadResult {
|
|
26
|
+
definition: SkillDefinition;
|
|
27
|
+
meta: {
|
|
28
|
+
source: SkillSource;
|
|
29
|
+
version?: string;
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
|
|
24
33
|
// ─── Skill Loader ────────────────────────────────────────
|
|
25
34
|
|
|
26
35
|
export class SkillLoader {
|
|
@@ -54,6 +63,30 @@ export class SkillLoader {
|
|
|
54
63
|
return merged;
|
|
55
64
|
}
|
|
56
65
|
|
|
66
|
+
/**
|
|
67
|
+
* Load skill definition with metadata about the source.
|
|
68
|
+
* Returns both the merged skill and info about where it came from.
|
|
69
|
+
*
|
|
70
|
+
* Args:
|
|
71
|
+
* role: The pipeline role to load a skill for.
|
|
72
|
+
*
|
|
73
|
+
* Returns:
|
|
74
|
+
* SkillLoadResult with definition and source metadata.
|
|
75
|
+
*/
|
|
76
|
+
loadSkillWithMeta(role: PipelineRole): SkillLoadResult {
|
|
77
|
+
const definition = this.loadSkill(role);
|
|
78
|
+
const override = this.loadMarkdownOverride(role);
|
|
79
|
+
const source: SkillSource = override?.systemPrompt ? 'project_override' : 'defaults';
|
|
80
|
+
|
|
81
|
+
return {
|
|
82
|
+
definition,
|
|
83
|
+
meta: {
|
|
84
|
+
source,
|
|
85
|
+
version: definition.version,
|
|
86
|
+
},
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
|
|
57
90
|
/** Load all skills for the given roles */
|
|
58
91
|
loadAllSkills(roles: PipelineRole[]): Map<PipelineRole, SkillDefinition> {
|
|
59
92
|
const result = new Map<PipelineRole, SkillDefinition>();
|