feat(orchestration): plan grounding, positive-pattern mining, stage-prompt audition rig
Vikunja #167/#168/#169. #167 PlanGrounder (infrastructure/workflow): build-prereq + touches-scope existence grounding, emits PlanGroundingEvaluatedEvent. Deterministic fold over recorded workspace/plan events (invariants #8/#9). #168 positive-pattern mining (SessionOrchestratorPlanPatterns): mine SuccessfulPlanShape from cross-session log — a locked plan whose own workflow later completed — and inject the closest-resembling plan shape as L0/SYSTEM advisory context for the planning stage. Keyword-Jaccard resemblance, no LLM/embedder, replays identically. Advisory only (#3). #169 stage-prompt audit + CORREX_STAGE_GUIDANCE ON/OFF toggle in SessionOrchestratorExecution. RunCommand gains --intent to seed a freestyle session over REST. Also: workspace verification events + concept-compiler wiring. ./gradlew check green.
This commit is contained in:
+10
-3
@@ -56,9 +56,12 @@ class ConceptCompilerService(
|
||||
payload = ConceptPromotedEvent(
|
||||
sessionId = SYSTEM_SESSION,
|
||||
fingerprint = cluster.fingerprint,
|
||||
classKey = cluster.classKey,
|
||||
gate = cluster.gate,
|
||||
conceptText = text,
|
||||
occurrences = cluster.validatedFixes,
|
||||
fixPath = cluster.fixPath,
|
||||
fixHash = cluster.fixHash,
|
||||
),
|
||||
),
|
||||
)
|
||||
@@ -85,9 +88,13 @@ class ConceptCompilerService(
|
||||
}
|
||||
}
|
||||
|
||||
private fun conceptText(c: ConceptCluster) =
|
||||
"Recurring ${c.gate} failure (validated-fixed ${c.validatedFixes}× across sessions): " +
|
||||
"${c.signature}. This failure class has a known resolution — check prior fixes before re-deriving."
|
||||
private fun conceptText(c: ConceptCluster): String {
|
||||
val resolution = c.fixPath?.let { path ->
|
||||
" Validated resolution in `$path`" + (c.fixHash?.let { "@$it" } ?: "") + " — inspect it before re-deriving."
|
||||
} ?: " This failure class has a known resolution — check prior fixes before re-deriving."
|
||||
return "Recurring ${c.gate} failure (validated-fixed ${c.validatedFixes}× across sessions): " +
|
||||
"${c.signature}.$resolution"
|
||||
}
|
||||
|
||||
private fun metadata() = EventMetadata(
|
||||
eventId = EventId(UUID.randomUUID().toString()),
|
||||
|
||||
@@ -7,7 +7,12 @@ import com.correx.core.events.events.EventMetadata
|
||||
import com.correx.core.events.events.ExecutionPlanLockedEvent
|
||||
import com.correx.core.events.events.ExecutionPlanRejectedEvent
|
||||
import com.correx.core.events.events.NewEvent
|
||||
import com.correx.core.events.events.PlanGroundingEvaluatedEvent
|
||||
import com.correx.core.events.events.PlanGroundingVerdict
|
||||
import com.correx.core.events.events.PlanLintCompletedEvent
|
||||
import com.correx.core.events.events.ProjectProfileBoundEvent
|
||||
import com.correx.core.events.events.RepoMapComputedEvent
|
||||
import com.correx.infrastructure.workflow.PlanGrounder
|
||||
import com.correx.core.tools.contract.ToolCapability
|
||||
import com.correx.infrastructure.workflow.CapabilityGap
|
||||
import com.correx.infrastructure.workflow.CapabilityGapDetector
|
||||
@@ -76,6 +81,11 @@ class FreestyleDriver(
|
||||
emitRejected(sessionId, "plan failed lint: $summary", "lint")
|
||||
return
|
||||
}
|
||||
// Plan grounding (design 2026-07-15 seam 1) before lock: check the compiled plan against the
|
||||
// session's recorded workspace facts (repo map + profile commands). A build stage aimed at a
|
||||
// prerequisite nothing creates is doomed — reject now so it fails at stage 0, not after
|
||||
// downstream files pile up. Deterministic + pure over recorded events (invariants #8/#9).
|
||||
if (groundPlan(sessionId, graph)) return
|
||||
// Capability-gap detector (Vikunja #30 part 1): advisory only — recorded, never blocking.
|
||||
// A gap does not fail the gate and does not grant the missing tool (invariants #3/#4/#5).
|
||||
val gaps = CapabilityGapDetector.detect(graph, toolCapabilities)
|
||||
@@ -132,6 +142,44 @@ class FreestyleDriver(
|
||||
.getOrNull()
|
||||
}
|
||||
|
||||
/**
|
||||
* Grounds [graph] against recorded workspace facts and emits [PlanGroundingEvaluatedEvent].
|
||||
* Returns true if the plan was rejected (caller must stop before lock). Missing repo map/profile
|
||||
* (fresh session, no scan) grounds vacuously — an empty path set with the manifest-produced-by-plan
|
||||
* check still catches the "build with nothing to build" case.
|
||||
*/
|
||||
private suspend fun groundPlan(sessionId: SessionId, graph: WorkflowGraph): Boolean {
|
||||
val events = eventStore.read(sessionId)
|
||||
val repoMap = events.mapNotNull { it.payload as? RepoMapComputedEvent }.lastOrNull()
|
||||
val profile = events.mapNotNull { it.payload as? ProjectProfileBoundEvent }.lastOrNull()
|
||||
val paths = repoMap?.entries?.map { it.path }?.toSet().orEmpty()
|
||||
val result = PlanGrounder.ground(graph, paths, profile?.commands.orEmpty())
|
||||
eventStore.append(
|
||||
NewEvent(
|
||||
metadata = EventMetadata(
|
||||
eventId = EventId(UUID.randomUUID().toString()),
|
||||
sessionId = sessionId,
|
||||
timestamp = Clock.System.now(),
|
||||
schemaVersion = 1,
|
||||
causationId = null,
|
||||
correlationId = null,
|
||||
),
|
||||
payload = PlanGroundingEvaluatedEvent(
|
||||
sessionId = sessionId,
|
||||
planId = graph.id,
|
||||
stateKey = repoMap?.stateKey.orEmpty(),
|
||||
verdict = result.verdict,
|
||||
findings = result.findings,
|
||||
),
|
||||
),
|
||||
)
|
||||
if (result.verdict == PlanGroundingVerdict.PASS) return false
|
||||
val summary = result.findings.joinToString("; ")
|
||||
log.warn("freestyle: plan failed grounding for session={}: {}", sessionId.value, summary)
|
||||
emitRejected(sessionId, "plan failed grounding: $summary", "grounding")
|
||||
return true
|
||||
}
|
||||
|
||||
private suspend fun emitPlanLint(sessionId: SessionId, candidateId: String, lint: PlanLintResult) {
|
||||
eventStore.append(
|
||||
NewEvent(
|
||||
|
||||
Reference in New Issue
Block a user