feat(router): idea-board -> project-profile promotion (E)
Promote an auto-captured idea from the cross-session board into the curated per-repo profile at <workspaceRoot>/.correx/project.toml as a `conventions` entry. New IdeaPromotedEvent tombstones the idea off the board (IdeaReader treats it like a discard) and records the promotion as a fact; the server PromoteIdea handler loads the profile, appends the idea text (deduped via ProjectProfile.withConvention), and writes it back. Adds ProjectProfileWriter (escape-aware TOML serialize/write) and makes SimpleToml's reader unescape \\ and \" symmetrically with the writers (fixes a pre-existing read/write asymmetry surfaced by the round-trip test). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -31,3 +31,19 @@ data class IdeaDiscardedEvent(
|
||||
val ideaId: String,
|
||||
val sessionId: SessionId,
|
||||
) : EventPayload
|
||||
|
||||
/**
|
||||
* The operator promoted an idea from the cross-session board into the curated per-repo profile
|
||||
* (`<workspaceRoot>/.correx/project.toml`, as a `conventions` entry). Like [IdeaDiscardedEvent] this
|
||||
* is a tombstone — the original [IdeaCapturedEvent] stays in the log (and replays), but the idea-board
|
||||
* projection filters out any promoted [ideaId]. [sessionId] is the capturing session (so all of one
|
||||
* idea's events stay co-located); [text] records the convention text written to the profile.
|
||||
*/
|
||||
@Serializable
|
||||
@SerialName("IdeaPromoted")
|
||||
data class IdeaPromotedEvent(
|
||||
val ideaId: String,
|
||||
val sessionId: SessionId,
|
||||
val text: String,
|
||||
val timestampMs: Long,
|
||||
) : EventPayload
|
||||
|
||||
@@ -29,6 +29,7 @@ import com.correx.core.events.events.HealthDegradedEvent
|
||||
import com.correx.core.events.events.HealthRestoredEvent
|
||||
import com.correx.core.events.events.IdeaCapturedEvent
|
||||
import com.correx.core.events.events.IdeaDiscardedEvent
|
||||
import com.correx.core.events.events.IdeaPromotedEvent
|
||||
import com.correx.core.events.events.JournalCompactedEvent
|
||||
import com.correx.core.events.events.FileWrittenEvent
|
||||
import com.correx.core.events.events.L3MemoryRetrievedEvent
|
||||
@@ -137,6 +138,7 @@ val eventModule = SerializersModule {
|
||||
subclass(WorkflowProposedEvent::class)
|
||||
subclass(IdeaCapturedEvent::class)
|
||||
subclass(IdeaDiscardedEvent::class)
|
||||
subclass(IdeaPromotedEvent::class)
|
||||
subclass(CritiqueOutcomeCorrelatedEvent::class)
|
||||
subclass(StageCheckpointPassedEvent::class)
|
||||
subclass(StageCheckpointFailedEvent::class)
|
||||
|
||||
@@ -6,6 +6,7 @@ import com.correx.core.approvals.Tier
|
||||
import com.correx.core.events.events.ApprovalDecisionResolvedEvent
|
||||
import com.correx.core.events.events.ApprovalRequestedEvent
|
||||
import com.correx.core.events.events.EventPayload
|
||||
import com.correx.core.events.events.IdeaPromotedEvent
|
||||
import com.correx.core.events.events.OrchestrationPausedEvent
|
||||
import com.correx.core.events.events.RiskAssessedEvent
|
||||
import com.correx.core.events.events.SteeringNoteAddedEvent
|
||||
@@ -81,6 +82,12 @@ class EventSerializationHardeningTest {
|
||||
terminalStageId = stageId,
|
||||
totalStages = 1,
|
||||
),
|
||||
"IdeaPromoted" to IdeaPromotedEvent(
|
||||
ideaId = "idea-1",
|
||||
sessionId = sessionId,
|
||||
text = "cache the repo map across sessions",
|
||||
timestampMs = 1_700_000_000_000,
|
||||
),
|
||||
)
|
||||
|
||||
@Test
|
||||
|
||||
Reference in New Issue
Block a user