@@ -150,6 +150,51 @@ export type RippleAgentHandoffVerdict = {
150150 commands : RippleAgentHandoffCommands ;
151151} ;
152152
153+ export type RippleReviewPacket = {
154+ protocol : "ripple-review-packet" ;
155+ version : 1 ;
156+ intentId : string ;
157+ originalTask : string ;
158+ mode : StagedCheckSummary [ "mode" ] ;
159+ declaredScope : {
160+ controlMode : ControlMode ;
161+ targetFile : string ;
162+ allowedFiles : string [ ] ;
163+ allowedSymbols : string [ ] ;
164+ humanGate : HumanGate ;
165+ boundaryRisk : ControlBoundaryRisk ;
166+ } ;
167+ actualChanges : {
168+ changedFiles : string [ ] ;
169+ changedSymbols : string [ ] ;
170+ contractRiskSymbols : string [ ] ;
171+ skippedFiles : string [ ] ;
172+ missingFiles : string [ ] ;
173+ } ;
174+ scopeFindings : {
175+ plannedFilesChanged : string [ ] ;
176+ contextFilesChanged : string [ ] ;
177+ outsideBoundaryFiles : string [ ] ;
178+ outsideBoundarySymbols : string [ ] ;
179+ protectedContractChanges : string [ ] ;
180+ unplannedContractChanges : string [ ] ;
181+ } ;
182+ verification : {
183+ expectedCommands : string [ ] ;
184+ testsRun : "unknown" ;
185+ note : string ;
186+ } ;
187+ decision : {
188+ canContinue : boolean ;
189+ mustStop : boolean ;
190+ needsHuman : boolean ;
191+ verdict : ChangeIntentVerdict ;
192+ nextRequiredAction : string ;
193+ recommendedAction : string ;
194+ } ;
195+ reviewerNotes : string [ ] ;
196+ } ;
197+
153198export type BoundaryVerdictStatus = "pass" | "drift" | "danger" ;
154199export type BoundaryDecision =
155200 | "continue"
@@ -208,6 +253,7 @@ export type ChangeIntentValidationSummary = {
208253
209254export type StagedCheckWithIntentSummary = StagedCheckSummary & {
210255 intentValidation ?: ChangeIntentValidationSummary ;
256+ reviewPacket ?: RippleReviewPacket ;
211257 nextRequiredPhase ?: AgentRuntimeNextPhaseId ;
212258 nextRequiredAction ?: string ;
213259} ;
@@ -457,11 +503,86 @@ export function validateStagedCheckAgainstIntent(
457503 ...staged ,
458504 requiresAttention : staged . requiresAttention || validation . requiresAttention ,
459505 intentValidation : validation ,
506+ reviewPacket : buildRippleReviewPacket ( staged , intent , validation ) ,
460507 nextRequiredPhase : validation . nextRequiredPhase ,
461508 nextRequiredAction : validation . nextRequiredAction ,
462509 } ;
463510}
464511
512+ export function buildRippleReviewPacket (
513+ staged : StagedCheckSummary ,
514+ intent : ChangeIntent ,
515+ validation : ChangeIntentValidationSummary
516+ ) : RippleReviewPacket {
517+ const changedFiles = uniqueItems ( [
518+ ...staged . files . map ( ( file ) => file . file ) ,
519+ ...staged . skippedFiles ,
520+ ...staged . missingFiles ,
521+ ] ) ;
522+ const changedSymbols = uniqueItems ( staged . changedSymbols . map ( ( symbol ) => symbol . symbol ) ) ;
523+ const contractRiskSymbols = uniqueItems ( staged . contractRisks . map ( ( risk ) => risk . symbol ) ) ;
524+ const outsideBoundaryFiles = uniqueItems ( [
525+ ...validation . unplannedFiles ,
526+ ...validation . boundaryVerdict . changedOutsideBoundaryFiles ,
527+ ] ) ;
528+ const outsideBoundarySymbols = uniqueItems ( [
529+ ...validation . unplannedSymbols ,
530+ ...validation . boundaryVerdict . changedOutsideBoundarySymbols ,
531+ ] ) ;
532+ const verificationTargets = uniqueItems ( [
533+ ...intent . verificationTargets ,
534+ ...staged . files . flatMap ( ( file ) => file . verificationTargets ) ,
535+ ] ) ;
536+ const mustStop = validation . handoff . mustStop ;
537+
538+ return {
539+ protocol : "ripple-review-packet" ,
540+ version : 1 ,
541+ intentId : intent . id ,
542+ originalTask : intent . task ,
543+ mode : staged . mode ,
544+ declaredScope : {
545+ controlMode : intent . controlMode ,
546+ targetFile : intent . targetFile ,
547+ allowedFiles : validation . editableFiles ,
548+ allowedSymbols : validation . allowedSymbols ,
549+ humanGate : validation . humanGate ,
550+ boundaryRisk : validation . boundaryRisk ,
551+ } ,
552+ actualChanges : {
553+ changedFiles,
554+ changedSymbols,
555+ contractRiskSymbols,
556+ skippedFiles : staged . skippedFiles ,
557+ missingFiles : staged . missingFiles ,
558+ } ,
559+ scopeFindings : {
560+ plannedFilesChanged : validation . plannedFilesChanged ,
561+ contextFilesChanged : validation . contextFilesChanged ,
562+ outsideBoundaryFiles,
563+ outsideBoundarySymbols,
564+ protectedContractChanges : validation . protectedContractChanges ,
565+ unplannedContractChanges : validation . unplannedContractChanges ,
566+ } ,
567+ verification : {
568+ expectedCommands : verificationTargets ,
569+ testsRun : "unknown" ,
570+ note : verificationTargets . length > 0
571+ ? "Ripple found verification targets, but it cannot prove they were run from this packet alone."
572+ : "Ripple found no verification target; use the narrowest manual check before handoff." ,
573+ } ,
574+ decision : {
575+ canContinue : validation . handoff . canContinue ,
576+ mustStop,
577+ needsHuman : validation . handoff . needsHuman ,
578+ verdict : validation . verdict ,
579+ nextRequiredAction : validation . nextRequiredAction ,
580+ recommendedAction : validation . recommendedAction ,
581+ } ,
582+ reviewerNotes : buildReviewPacketNotes ( validation , verificationTargets ) ,
583+ } ;
584+ }
585+
465586export function buildIntentDriftRepairPlan (
466587 staged : StagedCheckWithIntentSummary
467588) : IntentDriftRepairPlan {
@@ -1020,6 +1141,37 @@ function buildIntentValidation(
10201141 } ;
10211142}
10221143
1144+ function buildReviewPacketNotes (
1145+ validation : ChangeIntentValidationSummary ,
1146+ verificationTargets : string [ ]
1147+ ) : string [ ] {
1148+ const notes : string [ ] = [ ] ;
1149+ if ( validation . boundaryVerdict . changedOutsideBoundaryFiles . length > 0 ) {
1150+ notes . push ( "Patch scope crossed: review files outside the declared boundary before keeping this change." ) ;
1151+ }
1152+ if ( validation . boundaryVerdict . changedOutsideBoundarySymbols . length > 0 ) {
1153+ notes . push ( "Function scope crossed: review changed symbols outside the declared function boundary." ) ;
1154+ }
1155+ if ( validation . contextFilesChanged . length > 0 ) {
1156+ notes . push ( "Context-only files changed: approve a wider intent if those edits are valid." ) ;
1157+ }
1158+ if (
1159+ validation . protectedContractChanges . length > 0 ||
1160+ validation . unplannedContractChanges . length > 0
1161+ ) {
1162+ notes . push ( "Behavior scope requires review: protected or unplanned contracts changed." ) ;
1163+ }
1164+ if ( verificationTargets . length > 0 ) {
1165+ notes . push ( "Verification evidence is required before handoff; this packet records expected checks, not proof that they ran." ) ;
1166+ } else {
1167+ notes . push ( "No automated verification target was found; require a focused manual reviewer pass." ) ;
1168+ }
1169+ if ( validation . humanGate !== "none" ) {
1170+ notes . push ( `Human gate applies: ${ validation . humanGate } .` ) ;
1171+ }
1172+ return uniqueItems ( notes ) ;
1173+ }
1174+
10231175function validationNextRequiredPhase ( input : {
10241176 driftVerdict : DriftVerdictSummary ;
10251177 boundaryVerdict : BoundaryVerdictSummary ;
0 commit comments