popeye-cli 2.1.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 +328 -21
- package/dist/cli/interactive.js.map +1 -1
- package/dist/generators/all.d.ts.map +1 -1
- package/dist/generators/all.js +25 -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/artifact-manager.d.ts.map +1 -1
- package/dist/pipeline/artifact-manager.js +3 -0
- package/dist/pipeline/artifact-manager.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 +27 -8
- package/dist/pipeline/gate-engine.js.map +1 -1
- package/dist/pipeline/migration.d.ts.map +1 -1
- package/dist/pipeline/migration.js +3 -26
- package/dist/pipeline/migration.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 +311 -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 +1 -0
- package/dist/pipeline/phases/intake.d.ts.map +1 -1
- package/dist/pipeline/phases/intake.js +56 -13
- 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 +20 -5
- 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/constitution-generator.d.ts +51 -0
- package/dist/pipeline/skills/constitution-generator.d.ts.map +1 -0
- package/dist/pipeline/skills/constitution-generator.js +210 -0
- package/dist/pipeline/skills/constitution-generator.js.map +1 -0
- 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/generator.d.ts +65 -0
- package/dist/pipeline/skills/generator.d.ts.map +1 -0
- package/dist/pipeline/skills/generator.js +221 -0
- package/dist/pipeline/skills/generator.js.map +1 -0
- package/dist/pipeline/skills/role-map.d.ts +38 -0
- package/dist/pipeline/skills/role-map.d.ts.map +1 -0
- package/dist/pipeline/skills/role-map.js +234 -0
- package/dist/pipeline/skills/role-map.js.map +1 -0
- package/dist/pipeline/skills/types.d.ts +47 -0
- package/dist/pipeline/skills/types.d.ts.map +1 -0
- package/dist/pipeline/skills/types.js +5 -0
- package/dist/pipeline/skills/types.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 +30 -5
- package/dist/pipeline/type-defs/artifacts.d.ts.map +1 -1
- package/dist/pipeline/type-defs/artifacts.js +5 -0
- package/dist/pipeline/type-defs/artifacts.js.map +1 -1
- package/dist/pipeline/type-defs/audit.d.ts +28 -13
- package/dist/pipeline/type-defs/audit.d.ts.map +1 -1
- package/dist/pipeline/type-defs/checks.d.ts +19 -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 +119 -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 +165 -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 +337 -20
- package/src/generators/all.ts +25 -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/artifact-manager.ts +3 -0
- 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 +36 -8
- package/src/pipeline/migration.ts +5 -30
- package/src/pipeline/orchestrator.ts +367 -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 +67 -14
- 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 +23 -5
- 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/constitution-generator.ts +236 -0
- package/src/pipeline/skills/coverage-gate.ts +199 -0
- package/src/pipeline/skills/generator.ts +287 -0
- package/src/pipeline/skills/role-map.ts +248 -0
- package/src/pipeline/skills/types.ts +53 -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 +5 -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/migration.test.ts +4 -3
- 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/constitution-generator.test.ts +201 -0
- package/tests/pipeline/skills/coverage-gate.test.ts +370 -0
- package/tests/pipeline/skills/generator.test.ts +213 -0
- package/tests/pipeline/skills/role-map.test.ts +198 -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
|
@@ -19,13 +19,16 @@ import type {
|
|
|
19
19
|
PipelineResult,
|
|
20
20
|
RCAPacket,
|
|
21
21
|
} from './types.js';
|
|
22
|
-
import { createDefaultPipelineState } from './types.js';
|
|
22
|
+
import { createDefaultPipelineState, toLegacyPhase } from './types.js';
|
|
23
23
|
import { createGateEngine } from './gate-engine.js';
|
|
24
24
|
import type { GateResult } from './gate-engine.js';
|
|
25
25
|
import { createArtifactManager } from './artifact-manager.js';
|
|
26
26
|
import { createSkillLoader } from './skill-loader.js';
|
|
27
27
|
import { createConsensusRunner } from './consensus/consensus-runner.js';
|
|
28
28
|
import { verifyConstitution } from './constitution.js';
|
|
29
|
+
import { SkillUsageRegistry } from './skills/usage-registry.js';
|
|
30
|
+
import { runSkillCoverageCheck } from './check-runner.js';
|
|
31
|
+
import { resolveActiveCR, checkStagnation } from './cr-lifecycle.js';
|
|
29
32
|
|
|
30
33
|
import {
|
|
31
34
|
runIntake,
|
|
@@ -46,6 +49,7 @@ import {
|
|
|
46
49
|
import type { PhaseContext, PhaseResult } from './phases/index.js';
|
|
47
50
|
import type { ProjectState } from '../types/workflow.js';
|
|
48
51
|
import type { ConsensusConfig } from '../types/consensus.js';
|
|
52
|
+
import { updateState } from '../state/index.js';
|
|
49
53
|
|
|
50
54
|
// ─── Types ───────────────────────────────────────────────
|
|
51
55
|
|
|
@@ -97,19 +101,24 @@ export async function runPipeline(options: PipelineOptions): Promise<PipelineRes
|
|
|
97
101
|
} = options;
|
|
98
102
|
|
|
99
103
|
// Initialize pipeline state if needed
|
|
100
|
-
const pipeline: PipelineState =
|
|
101
|
-
|
|
104
|
+
const pipeline: PipelineState = state.pipeline ?? createDefaultPipelineState();
|
|
105
|
+
// Attach pipeline to state so persistence includes it
|
|
106
|
+
state.pipeline = pipeline;
|
|
102
107
|
|
|
103
108
|
// Persist user guidance in pipeline state so it survives resume
|
|
104
109
|
if (additionalContext && !pipeline.sessionGuidance) {
|
|
105
110
|
pipeline.sessionGuidance = additionalContext;
|
|
106
111
|
}
|
|
107
112
|
|
|
113
|
+
// Initialize skill usage events array (v2.2.1)
|
|
114
|
+
pipeline.skillUsageEvents = pipeline.skillUsageEvents ?? [];
|
|
115
|
+
const skillUsageRegistry = new SkillUsageRegistry(pipeline.skillUsageEvents);
|
|
116
|
+
|
|
108
117
|
// Create context dependencies
|
|
109
118
|
const gateEngine = createGateEngine();
|
|
110
119
|
const artifactManager = createArtifactManager(projectDir);
|
|
111
120
|
const skillLoader = createSkillLoader(projectDir);
|
|
112
|
-
const consensusRunner = createConsensusRunner(projectDir, consensusConfig);
|
|
121
|
+
const consensusRunner = createConsensusRunner(projectDir, consensusConfig, skillLoader, skillUsageRegistry);
|
|
113
122
|
|
|
114
123
|
// Ensure docs structure
|
|
115
124
|
artifactManager.ensureDocsStructure();
|
|
@@ -122,6 +131,7 @@ export async function runPipeline(options: PipelineOptions): Promise<PipelineRes
|
|
|
122
131
|
artifactManager,
|
|
123
132
|
gateEngine,
|
|
124
133
|
consensusRunner,
|
|
134
|
+
skillUsageRegistry,
|
|
125
135
|
};
|
|
126
136
|
|
|
127
137
|
let phase = pipeline.pipelinePhase;
|
|
@@ -159,6 +169,21 @@ export async function runPipeline(options: PipelineOptions): Promise<PipelineRes
|
|
|
159
169
|
|
|
160
170
|
onPhaseComplete?.(phase, result);
|
|
161
171
|
|
|
172
|
+
// Log phase outcome — critical for diagnosing pipeline loops
|
|
173
|
+
if (!result.success) {
|
|
174
|
+
onProgress?.(`Phase ${phase} FAILED: ${result.message}${result.error ? ` — ${result.error}` : ''}`);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// v2.2.1: Run skill coverage check before gates that require it
|
|
178
|
+
if (phase === 'CONSENSUS_ROLE_PLANS' || phase === 'PRODUCTION_GATE') {
|
|
179
|
+
const coverageResult = runSkillCoverageCheck(pipeline, phase);
|
|
180
|
+
const phaseChecks = pipeline.gateChecks[phase] ?? [];
|
|
181
|
+
// Replace any existing skill_coverage check result
|
|
182
|
+
const filtered = phaseChecks.filter((c) => c.check_type !== 'skill_coverage');
|
|
183
|
+
filtered.push(coverageResult);
|
|
184
|
+
pipeline.gateChecks[phase] = filtered;
|
|
185
|
+
}
|
|
186
|
+
|
|
162
187
|
// v1.1: Verify constitution integrity before evaluating gate
|
|
163
188
|
const constitutionCheck = verifyConstitution(pipeline, projectDir);
|
|
164
189
|
|
|
@@ -174,13 +199,42 @@ export async function runPipeline(options: PipelineOptions): Promise<PipelineRes
|
|
|
174
199
|
if (gateResult.pass) {
|
|
175
200
|
// ─── PASS ────────────────────────────────────────
|
|
176
201
|
|
|
202
|
+
// v2.7.0: Clear failedPhase when the originally-failed phase now passes.
|
|
203
|
+
// INVARIANT: This is the ONLY place failedPhase is cleared during the main loop.
|
|
204
|
+
// It must remain set throughout the entire recovery traversal to guard budget resets.
|
|
205
|
+
if (pipeline.failedPhase === phase) {
|
|
206
|
+
pipeline.failedPhase = undefined;
|
|
207
|
+
pipeline.recoveryBaselineFailedCheckCount = undefined;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// v2.4.9: Resolve the active CR after its routed phase passes
|
|
211
|
+
if (pipeline.activeChangeRequestId) {
|
|
212
|
+
resolveActiveCR(pipeline, onProgress);
|
|
213
|
+
}
|
|
214
|
+
|
|
177
215
|
// v1.1: Check for pending CRs after REVIEW/AUDIT — route to consensus before continuing
|
|
178
216
|
if (CR_CHECK_PHASES.has(phase)) {
|
|
179
217
|
const crRoute = getNextCRRoute(pipeline);
|
|
180
218
|
if (crRoute) {
|
|
181
219
|
onProgress?.(`CR ${crRoute.cr_id} routing to ${crRoute.target_phase}`);
|
|
220
|
+
// v2.5.4: CR routing to a new phase — reset recovery budget
|
|
221
|
+
if (crRoute.target_phase !== phase && pipeline.recoveryCount > 0) {
|
|
222
|
+
onProgress?.(`Recovery budget reset: ${pipeline.recoveryCount} -> 0 (CR routing ${phase} -> ${crRoute.target_phase})`);
|
|
223
|
+
pipeline.recoveryCount = 0;
|
|
224
|
+
pipeline.lastRewindTarget = undefined;
|
|
225
|
+
}
|
|
226
|
+
pipeline.activeChangeRequestId = crRoute.cr_id;
|
|
182
227
|
phase = crRoute.target_phase;
|
|
183
228
|
pipeline.pipelinePhase = phase;
|
|
229
|
+
// v2.5.0: Stagnation check before continue (otherwise skipped by the continue)
|
|
230
|
+
if (checkStagnation(pipeline, onProgress)) {
|
|
231
|
+
phase = 'STUCK';
|
|
232
|
+
pipeline.pipelinePhase = phase;
|
|
233
|
+
}
|
|
234
|
+
// Persist before continue (otherwise CR-routed phase is lost on crash)
|
|
235
|
+
try {
|
|
236
|
+
await updateState(projectDir, { pipeline, phase: toLegacyPhase(phase) });
|
|
237
|
+
} catch { /* non-fatal */ }
|
|
184
238
|
continue;
|
|
185
239
|
}
|
|
186
240
|
}
|
|
@@ -188,22 +242,121 @@ export async function runPipeline(options: PipelineOptions): Promise<PipelineRes
|
|
|
188
242
|
if (phase === 'RECOVERY_LOOP') {
|
|
189
243
|
// Recovery succeeded. RCA may specify rewind target.
|
|
190
244
|
const rca = getLatestRCA(pipeline, projectDir);
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
245
|
+
let rewindTarget = rca?.requires_phase_rewind_to;
|
|
246
|
+
|
|
247
|
+
// v2.4.6: Detect repeated same-target rewind — if we already rewound
|
|
248
|
+
// to this target last iteration and it didn't help, skip the rewind
|
|
249
|
+
// and re-test the failed phase directly.
|
|
250
|
+
if (rewindTarget && rewindTarget === pipeline.lastRewindTarget) {
|
|
251
|
+
onProgress?.(
|
|
252
|
+
`Repeated rewind to ${rewindTarget} detected ` +
|
|
253
|
+
`(recovery #${pipeline.recoveryCount}) — re-testing ` +
|
|
254
|
+
`${failedPhase ?? 'QA_VALIDATION'} directly`,
|
|
255
|
+
);
|
|
256
|
+
rewindTarget = undefined;
|
|
196
257
|
}
|
|
258
|
+
|
|
259
|
+
// Determine effective target
|
|
260
|
+
const effectiveTarget = rewindTarget ?? failedPhase ?? 'QA_VALIDATION';
|
|
261
|
+
phase = effectiveTarget;
|
|
262
|
+
|
|
263
|
+
// Record only the actual rewind target taken (not undefined when skipped)
|
|
264
|
+
pipeline.lastRewindTarget = rewindTarget;
|
|
265
|
+
|
|
266
|
+
// v2.4.6: Clear stale gate data so the re-entered phase evaluates fresh
|
|
267
|
+
if (failedPhase) {
|
|
268
|
+
delete pipeline.gateResults[failedPhase];
|
|
269
|
+
delete pipeline.gateChecks[failedPhase];
|
|
270
|
+
}
|
|
271
|
+
|
|
197
272
|
failedPhase = null;
|
|
198
273
|
} else {
|
|
199
274
|
// Normal progression
|
|
200
|
-
|
|
275
|
+
const nextPhase = gateEngine.getNextPhase(phase, gateResult);
|
|
276
|
+
|
|
277
|
+
// v2.5.4: Reset recovery budget on forward phase change.
|
|
278
|
+
// Each phase gets a fresh budget — prevents a single contentious consensus
|
|
279
|
+
// from consuming all iterations, leaving later phases with zero.
|
|
280
|
+
// v2.7.0: Don't reset budget during recovery traversal.
|
|
281
|
+
// failedPhase stays set until the originally-failed phase passes, so
|
|
282
|
+
// intermediate forward transitions (IMPL→QA→REVIEW→AUDIT) are skipped.
|
|
283
|
+
if (nextPhase !== phase && pipeline.recoveryCount > 0 && !pipeline.failedPhase) {
|
|
284
|
+
onProgress?.(`Recovery budget reset: ${pipeline.recoveryCount} -> 0 (advancing ${phase} -> ${nextPhase})`);
|
|
285
|
+
pipeline.recoveryCount = 0;
|
|
286
|
+
pipeline.lastRewindTarget = undefined;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
phase = nextPhase;
|
|
201
290
|
}
|
|
202
291
|
} else {
|
|
203
292
|
// ─── FAIL ────────────────────────────────────────
|
|
293
|
+
onProgress?.(`Gate FAILED for ${phase}: ${gateResult.blockers.join('; ')}`);
|
|
294
|
+
|
|
295
|
+
// v2.7.0: Regression detection — recovery made things worse than the original failure
|
|
296
|
+
if (
|
|
297
|
+
pipeline.failedPhase === phase &&
|
|
298
|
+
pipeline.recoveryBaselineFailedCheckCount !== undefined &&
|
|
299
|
+
(gateResult.failedChecks.length + gateResult.missingArtifacts.length) > pipeline.recoveryBaselineFailedCheckCount
|
|
300
|
+
) {
|
|
301
|
+
onProgress?.(
|
|
302
|
+
`[regression] Recovery worsened ${phase}: ` +
|
|
303
|
+
`${pipeline.recoveryBaselineFailedCheckCount} -> ` +
|
|
304
|
+
`${gateResult.failedChecks.length + gateResult.missingArtifacts.length} failing checks. ` +
|
|
305
|
+
`Treating budget as exhausted.`
|
|
306
|
+
);
|
|
307
|
+
pipeline.recoveryCount = pipeline.maxRecoveryIterations;
|
|
308
|
+
}
|
|
309
|
+
|
|
204
310
|
if (pipeline.recoveryCount >= pipeline.maxRecoveryIterations) {
|
|
205
|
-
|
|
311
|
+
const exhaustedPhase = phase; // capture before any reassignment
|
|
312
|
+
|
|
313
|
+
// v2.6.0: One auto-recovery attempt before STUCK
|
|
314
|
+
const arbitratorConfigured = !!(consensusConfig?.arbitrator);
|
|
315
|
+
if (!pipeline.autoRecoveryResult && arbitratorConfigured) {
|
|
316
|
+
onProgress?.(`[auto-recovery] Budget exhausted at ${exhaustedPhase}. Consulting arbitrator...`);
|
|
317
|
+
|
|
318
|
+
try {
|
|
319
|
+
const { attemptAutoRecovery } = await import('./auto-recovery.js');
|
|
320
|
+
const result = await attemptAutoRecovery({
|
|
321
|
+
pipeline, projectDir, artifactManager, consensusConfig,
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
pipeline.autoRecoveryResult = result.success ? 'success' : 'invalid';
|
|
325
|
+
if (result.artifact) pipeline.artifacts.push(result.artifact);
|
|
326
|
+
|
|
327
|
+
if (result.success && result.guidance) {
|
|
328
|
+
onProgress?.(`[auto-recovery] Strategic guidance received (${result.guidance.length} chars). Resetting budget.`);
|
|
329
|
+
pipeline.recoveryCount = 0;
|
|
330
|
+
pipeline.lastRewindTarget = undefined;
|
|
331
|
+
// Stay on the failed phase — let the normal loop re-execute it
|
|
332
|
+
// with the injected strategic guidance
|
|
333
|
+
phase = exhaustedPhase;
|
|
334
|
+
pipeline.pipelinePhase = phase;
|
|
335
|
+
pipeline.failedPhase = exhaustedPhase;
|
|
336
|
+
// Clear stale gate data so re-entry evaluates fresh
|
|
337
|
+
delete pipeline.gateResults[exhaustedPhase];
|
|
338
|
+
delete pipeline.gateChecks[exhaustedPhase];
|
|
339
|
+
// Continue main loop (don't fall through to STUCK)
|
|
340
|
+
} else {
|
|
341
|
+
onProgress?.(`[auto-recovery] No useful guidance. Entering STUCK.`);
|
|
342
|
+
phase = 'STUCK';
|
|
343
|
+
}
|
|
344
|
+
} catch (err) {
|
|
345
|
+
pipeline.autoRecoveryResult = (err as Error)?.message?.includes('timeout') ? 'timeout' : 'error';
|
|
346
|
+
onProgress?.(`[auto-recovery] Failed: ${err instanceof Error ? err.message : 'unknown'}. Entering STUCK.`);
|
|
347
|
+
phase = 'STUCK';
|
|
348
|
+
}
|
|
349
|
+
} else {
|
|
350
|
+
phase = 'STUCK';
|
|
351
|
+
}
|
|
206
352
|
} else {
|
|
353
|
+
// v2.7.0: Capture baseline for fresh failure or when failure origin changes
|
|
354
|
+
if (
|
|
355
|
+
pipeline.recoveryBaselineFailedCheckCount === undefined ||
|
|
356
|
+
pipeline.failedPhase !== phase // Different phase failing — new recovery origin
|
|
357
|
+
) {
|
|
358
|
+
pipeline.recoveryBaselineFailedCheckCount = gateResult.failedChecks.length + gateResult.missingArtifacts.length;
|
|
359
|
+
}
|
|
207
360
|
failedPhase = phase;
|
|
208
361
|
pipeline.failedPhase = phase;
|
|
209
362
|
phase = 'RECOVERY_LOOP';
|
|
@@ -211,8 +364,22 @@ export async function runPipeline(options: PipelineOptions): Promise<PipelineRes
|
|
|
211
364
|
}
|
|
212
365
|
}
|
|
213
366
|
|
|
367
|
+
// v2.5.0: Stagnation detection — shared helper checks rolling signature window
|
|
368
|
+
if (checkStagnation(pipeline, onProgress)) {
|
|
369
|
+
phase = 'STUCK';
|
|
370
|
+
}
|
|
371
|
+
|
|
214
372
|
// Update pipeline phase in state
|
|
215
373
|
pipeline.pipelinePhase = phase;
|
|
374
|
+
|
|
375
|
+
// v2.4.5b: Persist pipeline state after each phase transition (crash safety)
|
|
376
|
+
try {
|
|
377
|
+
await updateState(projectDir, { pipeline, phase: toLegacyPhase(phase) });
|
|
378
|
+
} catch (persistErr) {
|
|
379
|
+
onProgress?.(`Warning: Failed to persist pipeline state: ${
|
|
380
|
+
persistErr instanceof Error ? persistErr.message : String(persistErr)}`);
|
|
381
|
+
}
|
|
382
|
+
|
|
216
383
|
onProgress?.(`Transitioning to: ${phase}`);
|
|
217
384
|
}
|
|
218
385
|
|
|
@@ -229,6 +396,13 @@ export async function runPipeline(options: PipelineOptions): Promise<PipelineRes
|
|
|
229
396
|
}
|
|
230
397
|
}
|
|
231
398
|
|
|
399
|
+
// Persist final pipeline state (DONE or STUCK)
|
|
400
|
+
try {
|
|
401
|
+
await updateState(projectDir, { pipeline, phase: toLegacyPhase(phase) });
|
|
402
|
+
} catch {
|
|
403
|
+
// Best-effort for terminal persistence
|
|
404
|
+
}
|
|
405
|
+
|
|
232
406
|
return {
|
|
233
407
|
success: phase === 'DONE',
|
|
234
408
|
finalPhase: phase,
|
|
@@ -240,9 +414,185 @@ export async function runPipeline(options: PipelineOptions): Promise<PipelineRes
|
|
|
240
414
|
};
|
|
241
415
|
}
|
|
242
416
|
|
|
243
|
-
/** Resume pipeline from saved state */
|
|
417
|
+
/** Resume pipeline from saved state — with STUCK recovery (v2.4.5) */
|
|
244
418
|
export async function resumePipeline(options: PipelineOptions): Promise<PipelineResult> {
|
|
245
|
-
|
|
419
|
+
const pipeline: PipelineState | undefined = options.state.pipeline;
|
|
420
|
+
|
|
421
|
+
// v2.5.3: Normalize stale legacy status when pipeline is STUCK.
|
|
422
|
+
// completeProject() may have set status='complete' before the pipeline entered STUCK,
|
|
423
|
+
// leaving inconsistent state. Fix on load so the display layer never sees the lie.
|
|
424
|
+
if (
|
|
425
|
+
pipeline &&
|
|
426
|
+
(pipeline.pipelinePhase === 'STUCK' || pipeline.pipelinePhase === 'RECOVERY_LOOP') &&
|
|
427
|
+
options.state.status === 'complete'
|
|
428
|
+
) {
|
|
429
|
+
options.state.status = 'in-progress';
|
|
430
|
+
try {
|
|
431
|
+
await updateState(options.projectDir, { status: 'in-progress' });
|
|
432
|
+
} catch {
|
|
433
|
+
// Best-effort — display layer override in interactive.ts handles it regardless
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
// v2.4.8: RECOVERY_LOOP with remaining attempts — auto-resume without guidance.
|
|
438
|
+
// Must check this BEFORE the STUCK branch because RECOVERY_LOOP with exhausted
|
|
439
|
+
// attempts falls through to the guidance-required path below.
|
|
440
|
+
if (
|
|
441
|
+
pipeline?.pipelinePhase === 'RECOVERY_LOOP' &&
|
|
442
|
+
pipeline?.failedPhase &&
|
|
443
|
+
pipeline.recoveryCount < (pipeline.maxRecoveryIterations ?? 5)
|
|
444
|
+
) {
|
|
445
|
+
// Reset to the failed phase so the main loop re-enters. Do NOT reset
|
|
446
|
+
// recoveryCount — the pipeline retains its remaining iteration budget.
|
|
447
|
+
// Do NOT clear lastRewindTarget — preserve repeated-rewind detection.
|
|
448
|
+
pipeline.pipelinePhase = pipeline.failedPhase;
|
|
449
|
+
delete pipeline.gateResults[pipeline.failedPhase];
|
|
450
|
+
delete pipeline.gateChecks[pipeline.failedPhase];
|
|
451
|
+
options.onProgress?.(
|
|
452
|
+
`[resume] Auto-resuming from RECOVERY_LOOP: resetting to ${pipeline.failedPhase}, ` +
|
|
453
|
+
`recoveryCount ${pipeline.recoveryCount}/${pipeline.maxRecoveryIterations}`,
|
|
454
|
+
);
|
|
455
|
+
} else if (
|
|
456
|
+
// v2.4.7: STUCK (or RECOVERY_LOOP with exhausted attempts) requires user guidance.
|
|
457
|
+
(pipeline?.pipelinePhase === 'STUCK' ||
|
|
458
|
+
(pipeline?.pipelinePhase === 'RECOVERY_LOOP' &&
|
|
459
|
+
pipeline.recoveryCount >= (pipeline.maxRecoveryIterations ?? 5))) &&
|
|
460
|
+
pipeline?.failedPhase
|
|
461
|
+
) {
|
|
462
|
+
// v2.5.2: Purge legacy CRs without drift_key (from pre-v2.5.0 infinite loop).
|
|
463
|
+
// These are orphaned — no drift_key means they can never be deduplicated or resolved.
|
|
464
|
+
// Runs unconditionally (before guidance check) so cleanup persists even without guidance.
|
|
465
|
+
if (pipeline.pendingChangeRequests) {
|
|
466
|
+
const before = pipeline.pendingChangeRequests.length;
|
|
467
|
+
pipeline.pendingChangeRequests = pipeline.pendingChangeRequests.filter(
|
|
468
|
+
(cr) => cr.drift_key != null || cr.status === 'proposed',
|
|
469
|
+
);
|
|
470
|
+
const purged = before - pipeline.pendingChangeRequests.length;
|
|
471
|
+
if (purged > 0) {
|
|
472
|
+
options.onProgress?.(`[resume] Purged ${purged} legacy CRs without drift_key`);
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
// Clear stale activeChangeRequestId if the referenced CR was purged
|
|
477
|
+
if (
|
|
478
|
+
pipeline.activeChangeRequestId &&
|
|
479
|
+
!pipeline.pendingChangeRequests?.some((cr) => cr.cr_id === pipeline.activeChangeRequestId)
|
|
480
|
+
) {
|
|
481
|
+
options.onProgress?.(`[resume] Cleared stale activeChangeRequestId: ${pipeline.activeChangeRequestId}`);
|
|
482
|
+
pipeline.activeChangeRequestId = undefined;
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
const guidance = options.additionalContext?.trim() ?? '';
|
|
486
|
+
if (guidance.length > 0) {
|
|
487
|
+
// User provided guidance — allow one more retry
|
|
488
|
+
const prevRecovery = pipeline.recoveryCount;
|
|
489
|
+
pipeline.pipelinePhase = pipeline.failedPhase;
|
|
490
|
+
pipeline.recoveryCount = 0;
|
|
491
|
+
pipeline.lastRewindTarget = undefined;
|
|
492
|
+
pipeline.autoRecoveryResult = undefined; // v2.6.0: Fresh auto-recovery budget after user guidance
|
|
493
|
+
|
|
494
|
+
// Clear stale gate results and checks for the failed phase
|
|
495
|
+
delete pipeline.gateResults[pipeline.failedPhase];
|
|
496
|
+
delete pipeline.gateChecks[pipeline.failedPhase];
|
|
497
|
+
|
|
498
|
+
options.onProgress?.(
|
|
499
|
+
`[resume] Recovering from ${pipeline.pipelinePhase}: resetting to ${pipeline.failedPhase}, ` +
|
|
500
|
+
`recoveryCount ${prevRecovery} -> 0 (user provided guidance)`,
|
|
501
|
+
);
|
|
502
|
+
} else {
|
|
503
|
+
// No guidance — v2.6.0: attempt auto-recovery before giving up
|
|
504
|
+
const arbitratorConfigured = !!(options.consensusConfig?.arbitrator);
|
|
505
|
+
if (!pipeline.autoRecoveryResult && arbitratorConfigured) {
|
|
506
|
+
options.onProgress?.(
|
|
507
|
+
`[resume] No guidance provided. Attempting auto-recovery via arbitrator...`,
|
|
508
|
+
);
|
|
509
|
+
|
|
510
|
+
try {
|
|
511
|
+
const artifactManager = createArtifactManager(options.projectDir);
|
|
512
|
+
const { attemptAutoRecovery } = await import('./auto-recovery.js');
|
|
513
|
+
const result = await attemptAutoRecovery({
|
|
514
|
+
pipeline,
|
|
515
|
+
projectDir: options.projectDir,
|
|
516
|
+
artifactManager,
|
|
517
|
+
consensusConfig: options.consensusConfig,
|
|
518
|
+
});
|
|
519
|
+
|
|
520
|
+
pipeline.autoRecoveryResult = result.success ? 'success' : 'invalid';
|
|
521
|
+
if (result.artifact) pipeline.artifacts.push(result.artifact);
|
|
522
|
+
|
|
523
|
+
if (result.success && result.guidance) {
|
|
524
|
+
options.onProgress?.(
|
|
525
|
+
`[resume] Auto-recovery guidance received (${result.guidance.length} chars). Resetting budget.`,
|
|
526
|
+
);
|
|
527
|
+
const prevRecovery = pipeline.recoveryCount;
|
|
528
|
+
pipeline.pipelinePhase = pipeline.failedPhase;
|
|
529
|
+
pipeline.recoveryCount = 0;
|
|
530
|
+
pipeline.lastRewindTarget = undefined;
|
|
531
|
+
delete pipeline.gateResults[pipeline.failedPhase];
|
|
532
|
+
delete pipeline.gateChecks[pipeline.failedPhase];
|
|
533
|
+
|
|
534
|
+
options.onProgress?.(
|
|
535
|
+
`[resume] Recovering from STUCK: resetting to ${pipeline.failedPhase}, ` +
|
|
536
|
+
`recoveryCount ${prevRecovery} -> 0 (auto-recovery guidance)`,
|
|
537
|
+
);
|
|
538
|
+
// Fall through to runPipeline() below
|
|
539
|
+
} else {
|
|
540
|
+
options.onProgress?.(`[resume] Auto-recovery produced no useful guidance.`);
|
|
541
|
+
// Fall through to return STUCK below
|
|
542
|
+
}
|
|
543
|
+
} catch (err) {
|
|
544
|
+
pipeline.autoRecoveryResult = (err as Error)?.message?.includes('timeout') ? 'timeout' : 'error';
|
|
545
|
+
options.onProgress?.(
|
|
546
|
+
`[resume] Auto-recovery failed: ${err instanceof Error ? err.message : 'unknown'}`,
|
|
547
|
+
);
|
|
548
|
+
// Fall through to return STUCK below
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
// Persist state (whether auto-recovery succeeded or not)
|
|
552
|
+
try {
|
|
553
|
+
await updateState(options.projectDir, { pipeline });
|
|
554
|
+
} catch { /* best-effort */ }
|
|
555
|
+
|
|
556
|
+
// If auto-recovery succeeded, fall through to runPipeline()
|
|
557
|
+
if (pipeline.autoRecoveryResult === 'success') {
|
|
558
|
+
return runPipeline(options);
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
// No guidance, no auto-recovery (or auto-recovery failed) — return STUCK
|
|
563
|
+
try {
|
|
564
|
+
await updateState(options.projectDir, { pipeline });
|
|
565
|
+
} catch {
|
|
566
|
+
// Best-effort — cleanup is still in memory for next resume
|
|
567
|
+
}
|
|
568
|
+
options.onProgress?.(
|
|
569
|
+
`[resume] Pipeline is stuck at ${pipeline.failedPhase} after ${pipeline.recoveryCount} recovery attempts. ` +
|
|
570
|
+
`Provide guidance to attempt recovery.`,
|
|
571
|
+
);
|
|
572
|
+
return {
|
|
573
|
+
success: false,
|
|
574
|
+
finalPhase: 'STUCK',
|
|
575
|
+
artifacts: pipeline.artifacts,
|
|
576
|
+
recoveryIterations: pipeline.recoveryCount,
|
|
577
|
+
error: `Pipeline stuck at ${pipeline.failedPhase} after ${pipeline.recoveryCount} recovery iterations. Provide guidance to retry.`,
|
|
578
|
+
};
|
|
579
|
+
}
|
|
580
|
+
} else if (
|
|
581
|
+
(pipeline?.pipelinePhase === 'STUCK' || pipeline?.pipelinePhase === 'RECOVERY_LOOP') &&
|
|
582
|
+
!pipeline?.failedPhase
|
|
583
|
+
) {
|
|
584
|
+
options.onProgress?.(
|
|
585
|
+
`[resume] Pipeline is ${pipeline.pipelinePhase} but failedPhase is missing — cannot auto-recover`,
|
|
586
|
+
);
|
|
587
|
+
return {
|
|
588
|
+
success: false,
|
|
589
|
+
finalPhase: 'STUCK',
|
|
590
|
+
artifacts: pipeline?.artifacts ?? [],
|
|
591
|
+
recoveryIterations: pipeline?.recoveryCount ?? 0,
|
|
592
|
+
error: 'Pipeline is stuck with no failed phase recorded. Manual intervention required.',
|
|
593
|
+
};
|
|
594
|
+
}
|
|
595
|
+
|
|
246
596
|
return runPipeline(options);
|
|
247
597
|
}
|
|
248
598
|
|
|
@@ -259,12 +609,13 @@ function mergeGateResult(
|
|
|
259
609
|
gateResult: GateResult,
|
|
260
610
|
): void {
|
|
261
611
|
const existing = pipeline.gateResults[phase];
|
|
262
|
-
if (existing?.score !== undefined || existing?.consensusScore !== undefined) {
|
|
263
|
-
// Preserve consensus scores from the phase handler
|
|
612
|
+
if (existing?.score !== undefined || existing?.consensusScore !== undefined || existing?.finalStatus !== undefined) {
|
|
613
|
+
// Preserve consensus scores and finalStatus from the phase handler
|
|
264
614
|
pipeline.gateResults[phase] = {
|
|
265
615
|
...gateResult,
|
|
266
616
|
score: existing.score ?? gateResult.score,
|
|
267
617
|
consensusScore: existing.consensusScore ?? gateResult.consensusScore,
|
|
618
|
+
finalStatus: existing.finalStatus ?? gateResult.finalStatus, // v2.4.3
|
|
268
619
|
};
|
|
269
620
|
} else {
|
|
270
621
|
pipeline.gateResults[phase] = gateResult;
|
|
@@ -284,7 +635,7 @@ function getNextCRRoute(
|
|
|
284
635
|
const nextCR = pending.find((cr) => cr.status === 'proposed');
|
|
285
636
|
if (!nextCR) return undefined;
|
|
286
637
|
|
|
287
|
-
// Mark as
|
|
638
|
+
// Mark as routed/in-flight (kept as 'approved' for backward compatibility)
|
|
288
639
|
nextCR.status = 'approved';
|
|
289
640
|
|
|
290
641
|
return {
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Consensus Packet Builder — aggregates reviewer votes into a ConsensusPacket.
|
|
3
3
|
* Auto-computes consensus result (score, approval) and final status.
|
|
4
|
-
*
|
|
4
|
+
* v2.0: Option B scoring — avg(baseWeight * confidence) per reviewer.
|
|
5
|
+
* REJECT guard: REJECT with true blockers prevents APPROVED (not ARBITRATED).
|
|
6
|
+
* Normalization summary passthrough.
|
|
5
7
|
*/
|
|
6
8
|
|
|
7
9
|
import { randomUUID } from 'node:crypto';
|
|
@@ -11,6 +13,7 @@ import type {
|
|
|
11
13
|
ReviewerVote,
|
|
12
14
|
ConsensusPacket,
|
|
13
15
|
} from '../types.js';
|
|
16
|
+
import { isNoneVariant } from '../../shared/text-utils.js';
|
|
14
17
|
|
|
15
18
|
export interface ConsensusRules {
|
|
16
19
|
threshold: number;
|
|
@@ -18,6 +21,13 @@ export interface ConsensusRules {
|
|
|
18
21
|
min_reviewers: number;
|
|
19
22
|
}
|
|
20
23
|
|
|
24
|
+
export interface NormalizationSummary {
|
|
25
|
+
tagged_blockers_demoted_to_suggestions: number;
|
|
26
|
+
tagged_blockers_demoted_to_required: number;
|
|
27
|
+
untagged_from_blocking_routed_to_required: number;
|
|
28
|
+
forced_rejects: number;
|
|
29
|
+
}
|
|
30
|
+
|
|
21
31
|
export interface BuildConsensusPacketArgs {
|
|
22
32
|
planPacketRef: ArtifactRef;
|
|
23
33
|
votes: ReviewerVote[];
|
|
@@ -27,6 +37,7 @@ export interface BuildConsensusPacketArgs {
|
|
|
27
37
|
merged_patch?: string;
|
|
28
38
|
artifact_ref?: ArtifactRef;
|
|
29
39
|
};
|
|
40
|
+
normalizationMoves?: NormalizationSummary;
|
|
30
41
|
}
|
|
31
42
|
|
|
32
43
|
/** Vote weight mapping: APPROVE=1.0, CONDITIONAL=0.5, REJECT=0.0 */
|
|
@@ -40,43 +51,48 @@ const VOTE_WEIGHTS: Record<string, number> = {
|
|
|
40
51
|
* Compute both simple and confidence-weighted consensus scores.
|
|
41
52
|
*
|
|
42
53
|
* Simple score: approve count / total votes (backward compat).
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
54
|
+
* Option B weighted score: average of (vote_weight * confidence) per reviewer.
|
|
55
|
+
* v2.4.2: Returns honest rawWeighted score + has_true_blockers flag.
|
|
56
|
+
* Force-zero veto removed — governance guard in buildConsensusPacket() is
|
|
57
|
+
* the single enforcement point for blocker-based rejection.
|
|
46
58
|
*/
|
|
47
59
|
export function computeConsensusScore(
|
|
48
60
|
votes: ReviewerVote[],
|
|
49
|
-
): { score: number; weighted_score: number } {
|
|
50
|
-
if (votes.length === 0) return { score: 0, weighted_score: 0 };
|
|
61
|
+
): { score: number; weighted_score: number; has_true_blockers: boolean } {
|
|
62
|
+
if (votes.length === 0) return { score: 0, weighted_score: 0, has_true_blockers: false };
|
|
51
63
|
|
|
52
64
|
// Simple score (backward compat): approve ratio
|
|
53
65
|
const approvedCount = votes.filter((v) => v.vote === 'APPROVE').length;
|
|
54
66
|
const score = approvedCount / votes.length;
|
|
55
67
|
|
|
56
|
-
//
|
|
57
|
-
let totalWeight = 0;
|
|
68
|
+
// Option B scoring: average of (vote_weight * confidence) per reviewer
|
|
58
69
|
let weightedSum = 0;
|
|
59
70
|
for (const v of votes) {
|
|
60
|
-
const
|
|
61
|
-
weightedSum +=
|
|
62
|
-
totalWeight += w;
|
|
71
|
+
const baseWeight = VOTE_WEIGHTS[v.vote] ?? 0;
|
|
72
|
+
weightedSum += baseWeight * v.confidence;
|
|
63
73
|
}
|
|
64
|
-
const rawWeighted =
|
|
74
|
+
const rawWeighted = weightedSum / votes.length;
|
|
65
75
|
|
|
66
|
-
//
|
|
67
|
-
|
|
76
|
+
// v2.4.2: Detect true blockers but don't force score to 0.
|
|
77
|
+
// Downstream code uses has_true_blockers for decisions instead.
|
|
78
|
+
const has_true_blockers = votes.some(
|
|
79
|
+
(v) => v.vote === 'REJECT' && v.blocking_issues.some((issue) => !isNoneVariant(issue)),
|
|
80
|
+
);
|
|
68
81
|
|
|
69
82
|
return {
|
|
70
83
|
score,
|
|
71
|
-
weighted_score:
|
|
84
|
+
weighted_score: rawWeighted,
|
|
85
|
+
has_true_blockers,
|
|
72
86
|
};
|
|
73
87
|
}
|
|
74
88
|
|
|
75
89
|
export function buildConsensusPacket(args: BuildConsensusPacketArgs): ConsensusPacket {
|
|
76
|
-
const { planPacketRef, votes, rules, arbitratorResult } = args;
|
|
90
|
+
const { planPacketRef, votes, rules, arbitratorResult, normalizationMoves } = args;
|
|
91
|
+
|
|
92
|
+
const { score, weighted_score, has_true_blockers } = computeConsensusScore(votes);
|
|
77
93
|
|
|
78
|
-
|
|
79
|
-
const approved =
|
|
94
|
+
// Use weighted_score (not simple score) for approval decision
|
|
95
|
+
const approved = weighted_score >= rules.threshold && votes.length >= rules.quorum;
|
|
80
96
|
|
|
81
97
|
let finalStatus: 'APPROVED' | 'REJECTED' | 'ARBITRATED';
|
|
82
98
|
if (arbitratorResult) {
|
|
@@ -87,6 +103,14 @@ export function buildConsensusPacket(args: BuildConsensusPacketArgs): ConsensusP
|
|
|
87
103
|
finalStatus = 'REJECTED';
|
|
88
104
|
}
|
|
89
105
|
|
|
106
|
+
// Governance guard: REJECT with true blockers prevents APPROVED (but not ARBITRATED)
|
|
107
|
+
const hasRejectWithTrueBlockers = votes.some(
|
|
108
|
+
(v) => v.vote === 'REJECT' && v.blocking_issues.some((i) => !isNoneVariant(i)),
|
|
109
|
+
);
|
|
110
|
+
if (hasRejectWithTrueBlockers && finalStatus === 'APPROVED') {
|
|
111
|
+
finalStatus = 'REJECTED';
|
|
112
|
+
}
|
|
113
|
+
|
|
90
114
|
return {
|
|
91
115
|
metadata: {
|
|
92
116
|
packet_id: randomUUID(),
|
|
@@ -105,8 +129,10 @@ export function buildConsensusPacket(args: BuildConsensusPacketArgs): ConsensusP
|
|
|
105
129
|
score,
|
|
106
130
|
weighted_score,
|
|
107
131
|
participating_reviewers: votes.length,
|
|
132
|
+
has_true_blockers,
|
|
108
133
|
},
|
|
109
134
|
arbitrator_result: arbitratorResult,
|
|
110
135
|
final_status: finalStatus,
|
|
136
|
+
normalization_moves: normalizationMoves,
|
|
111
137
|
};
|
|
112
138
|
}
|
|
@@ -11,12 +11,12 @@ import { successResult, failureResult } from './phase-context.js';
|
|
|
11
11
|
import { generateRepoSnapshot, createSnapshotArtifact } from '../repo-snapshot.js';
|
|
12
12
|
|
|
13
13
|
export async function runArchitecture(context: PhaseContext): Promise<PhaseResult> {
|
|
14
|
-
const { pipeline, artifactManager, skillLoader, projectDir } = context;
|
|
14
|
+
const { pipeline, artifactManager, skillLoader, skillUsageRegistry, projectDir } = context;
|
|
15
15
|
const artifacts = [];
|
|
16
16
|
|
|
17
17
|
try {
|
|
18
|
-
// 1. Load architect skill
|
|
19
|
-
const architectSkill = skillLoader.
|
|
18
|
+
// 1. Load architect skill with metadata
|
|
19
|
+
const { definition: architectSkill, meta: architectMeta } = skillLoader.loadSkillWithMeta('ARCHITECT');
|
|
20
20
|
|
|
21
21
|
// 2. Read approved master plan
|
|
22
22
|
const masterPlanArtifact = pipeline.artifacts.find((a) => a.type === 'master_plan');
|
|
@@ -50,6 +50,9 @@ export async function runArchitecture(context: PhaseContext): Promise<PhaseResul
|
|
|
50
50
|
const result = await executePrompt(architecturePrompt);
|
|
51
51
|
const architectureDoc = result.response;
|
|
52
52
|
|
|
53
|
+
// Record skill usage — architect skill injected into prompt
|
|
54
|
+
skillUsageRegistry.record('ARCHITECT', 'ARCHITECTURE', 'system_prompt', architectMeta.source, architectMeta.version);
|
|
55
|
+
|
|
53
56
|
// 4. Store architecture artifact
|
|
54
57
|
const archEntry = artifactManager.createAndStoreText(
|
|
55
58
|
'architecture',
|