diff --git a/cli/session/pipeline_phase.go b/cli/session/pipeline_phase.go new file mode 100644 index 0000000..d5bef41 --- /dev/null +++ b/cli/session/pipeline_phase.go @@ -0,0 +1,36 @@ +package session + +// PipelinePhase identifies the step of the Agentless-style pipeline +// (localize → repair → validate) that a session turn belongs to. +// +// These string constants mirror hawk-core-contracts/sessions.Phase so that +// cost records emitted by trace can be correlated with hawk's tok.Tracker +// AggregateByPhase output without importing the contracts package +// (trace is a standalone CLI, not part of the hawk Go module graph). +type PipelinePhase string + +const ( + // PipelinePhaseLocalize is the file/symbol identification phase. + PipelinePhaseLocalize PipelinePhase = "localize" + // PipelinePhaseRepair is the patch generation phase. + PipelinePhaseRepair PipelinePhase = "repair" + // PipelinePhaseValidate is the test/lint verification phase. + PipelinePhaseValidate PipelinePhase = "validate" + // PipelinePhaseReview is code review; the most token-intensive phase + // at 59.4% of total spend (Tokenomics paper, arXiv 2601.14470). + PipelinePhaseReview PipelinePhase = "review" + // PipelinePhaseUnknown is the zero value for unattributed turns. + PipelinePhaseUnknown PipelinePhase = "" +) + +// ParsePipelinePhase returns the PipelinePhase matching s, or +// PipelinePhaseUnknown if unrecognised. Matching is case-sensitive to +// stay consistent with the contracts package and JSON encoding. +func ParsePipelinePhase(s string) PipelinePhase { + switch PipelinePhase(s) { + case PipelinePhaseLocalize, PipelinePhaseRepair, + PipelinePhaseValidate, PipelinePhaseReview: + return PipelinePhase(s) + } + return PipelinePhaseUnknown +} diff --git a/cli/session/pipeline_phase_test.go b/cli/session/pipeline_phase_test.go new file mode 100644 index 0000000..5a6958f --- /dev/null +++ b/cli/session/pipeline_phase_test.go @@ -0,0 +1,39 @@ +package session_test + +import ( + "testing" + + "github.com/GrayCodeAI/trace/cli/session" +) + +func TestParsePipelinePhase_Known(t *testing.T) { + cases := []struct { + input string + want session.PipelinePhase + }{ + {"localize", session.PipelinePhaseLocalize}, + {"repair", session.PipelinePhaseRepair}, + {"validate", session.PipelinePhaseValidate}, + {"review", session.PipelinePhaseReview}, + } + for _, tc := range cases { + got := session.ParsePipelinePhase(tc.input) + if got != tc.want { + t.Errorf("ParsePipelinePhase(%q) = %q, want %q", tc.input, got, tc.want) + } + } +} + +func TestParsePipelinePhase_Unknown(t *testing.T) { + for _, s := range []string{"", "unknown", "LOCALIZE", "Repair"} { + if got := session.ParsePipelinePhase(s); got != session.PipelinePhaseUnknown { + t.Errorf("ParsePipelinePhase(%q) = %q, want PipelinePhaseUnknown", s, got) + } + } +} + +func TestPipelinePhaseString(t *testing.T) { + if string(session.PipelinePhaseReview) != "review" { + t.Errorf("PipelinePhaseReview string = %q, want %q", session.PipelinePhaseReview, "review") + } +}