From 38d5cff0e53f29de86720a49862ef09c82d0d129 Mon Sep 17 00:00:00 2001 From: Snider Date: Sat, 25 Apr 2026 02:37:22 +0100 Subject: [PATCH] =?UTF-8?q?release:=20v0.8.0-alpha.1=20=E2=80=94=20module?= =?UTF-8?q?=20migration,=20AX-6=20testify=20replacement,=20AX-10=20scaffol?= =?UTF-8?q?d?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Consolidates the v0.8.0-alpha.1 release work for core/go-session. * Module path migration: forge.lthn.ai/core/go-session -> dappco.re/go/session. All sibling dappco.re/go/* deps pinned to v0.8.0-alpha.1. * AX-6 testify removal: replaced testify assertions with stdlib testing patterns across the test suite. Removes external test framework dep in favour of the universal Core test contract. * AX-6 annotation: intrinsic banned imports annotated where their use is structurally required (boundary functions, justified by comment). * AX-10 scaffold: tests/cli/session/Taskfile.yaml + test driver for the session CLI surface. * Stale path cleanup: minor housekeeping of references to the pre-migration module path. Refs: RFC-CORE-008-AGENT-EXPERIENCE.md (AX-1, AX-6, AX-10) Co-authored-by: Athena Co-authored-by: Hephaestus Co-authored-by: Cladius Maximus --- analytics.go | 6 +- analytics_test.go | 101 +++--- conventions_test.go | 2 +- core_helpers.go | 2 +- go.mod | 14 +- go.sum | 12 - html.go | 6 +- html_test.go | 84 +++-- parser.go | 16 +- parser_test.go | 544 ++++++++++++++++---------------- search.go | 8 +- search_test.go | 55 ++-- test_helpers_test.go | 179 +++++++++++ tests/cli/session/Taskfile.yaml | 14 + tests/cli/session/main.go | 82 +++++ video.go | 6 +- video_test.go | 56 ++-- 17 files changed, 714 insertions(+), 473 deletions(-) create mode 100644 test_helpers_test.go create mode 100644 tests/cli/session/Taskfile.yaml create mode 100644 tests/cli/session/main.go diff --git a/analytics.go b/analytics.go index 98d2af7..40b4d14 100644 --- a/analytics.go +++ b/analytics.go @@ -2,9 +2,9 @@ package session import ( - "maps" - "slices" - "time" + "maps" // Note: intrinsic — maps.Keys exposes tool names for deterministic analytics output; no core equivalent + "slices" // Note: intrinsic — slices.Sorted orders analytics rows deterministically; no core equivalent + "time" // Note: intrinsic — time.Duration arithmetic for session, active-time, and latency metrics; no core equivalent core "dappco.re/go/core" ) diff --git a/analytics_test.go b/analytics_test.go index 8900f84..b9e7499 100644 --- a/analytics_test.go +++ b/analytics_test.go @@ -4,9 +4,6 @@ package session import ( "testing" "time" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" ) func TestAnalytics_AnalyseEmptySession_Good(t *testing.T) { @@ -18,22 +15,22 @@ func TestAnalytics_AnalyseEmptySession_Good(t *testing.T) { } a := Analyse(sess) - require.NotNil(t, a) + requireNotNil(t, a) - assert.Equal(t, time.Duration(0), a.Duration) - assert.Equal(t, time.Duration(0), a.ActiveTime) - assert.Equal(t, 0, a.EventCount) - assert.Equal(t, 0.0, a.SuccessRate) - assert.Empty(t, a.ToolCounts) - assert.Empty(t, a.ErrorCounts) - assert.Equal(t, 0, a.EstimatedInputTokens) - assert.Equal(t, 0, a.EstimatedOutputTokens) + assertEqual(t, time.Duration(0), a.Duration) + assertEqual(t, time.Duration(0), a.ActiveTime) + assertEqual(t, 0, a.EventCount) + assertEqual(t, 0.0, a.SuccessRate) + assertEmpty(t, a.ToolCounts) + assertEmpty(t, a.ErrorCounts) + assertEqual(t, 0, a.EstimatedInputTokens) + assertEqual(t, 0, a.EstimatedOutputTokens) } func TestAnalytics_AnalyseNilSession_Good(t *testing.T) { a := Analyse(nil) - require.NotNil(t, a) - assert.Equal(t, 0, a.EventCount) + requireNotNil(t, a) + assertEqual(t, 0, a.EventCount) } func TestAnalytics_AnalyseSingleToolCall_Good(t *testing.T) { @@ -56,14 +53,14 @@ func TestAnalytics_AnalyseSingleToolCall_Good(t *testing.T) { a := Analyse(sess) - assert.Equal(t, 5*time.Second, a.Duration) - assert.Equal(t, 2*time.Second, a.ActiveTime) - assert.Equal(t, 1, a.EventCount) - assert.Equal(t, 1.0, a.SuccessRate) - assert.Equal(t, 1, a.ToolCounts["Bash"]) - assert.Equal(t, 0, a.ErrorCounts["Bash"]) - assert.Equal(t, 2*time.Second, a.AvgLatency["Bash"]) - assert.Equal(t, 2*time.Second, a.MaxLatency["Bash"]) + assertEqual(t, 5*time.Second, a.Duration) + assertEqual(t, 2*time.Second, a.ActiveTime) + assertEqual(t, 1, a.EventCount) + assertEqual(t, 1.0, a.SuccessRate) + assertEqual(t, 1, a.ToolCounts["Bash"]) + assertEqual(t, 0, a.ErrorCounts["Bash"]) + assertEqual(t, 2*time.Second, a.AvgLatency["Bash"]) + assertEqual(t, 2*time.Second, a.MaxLatency["Bash"]) } func TestAnalytics_AnalyseMixedToolsWithErrors_Good(t *testing.T) { @@ -127,24 +124,24 @@ func TestAnalytics_AnalyseMixedToolsWithErrors_Good(t *testing.T) { a := Analyse(sess) - assert.Equal(t, 5*time.Minute, a.Duration) - assert.Equal(t, 7, a.EventCount) + assertEqual(t, 5*time.Minute, a.Duration) + assertEqual(t, 7, a.EventCount) // Tool counts - assert.Equal(t, 2, a.ToolCounts["Bash"]) - assert.Equal(t, 2, a.ToolCounts["Read"]) - assert.Equal(t, 1, a.ToolCounts["Edit"]) + assertEqual(t, 2, a.ToolCounts["Bash"]) + assertEqual(t, 2, a.ToolCounts["Read"]) + assertEqual(t, 1, a.ToolCounts["Edit"]) // Error counts - assert.Equal(t, 1, a.ErrorCounts["Bash"]) - assert.Equal(t, 1, a.ErrorCounts["Read"]) - assert.Equal(t, 0, a.ErrorCounts["Edit"]) + assertEqual(t, 1, a.ErrorCounts["Bash"]) + assertEqual(t, 1, a.ErrorCounts["Read"]) + assertEqual(t, 0, a.ErrorCounts["Edit"]) // Success rate: 3 successes out of 5 tool calls = 0.6 - assert.InDelta(t, 0.6, a.SuccessRate, 0.001) + assertInDelta(t, 0.6, a.SuccessRate, 0.001) // Active time: 1s + 500ms + 200ms + 100ms + 300ms = 2.1s - assert.Equal(t, 2100*time.Millisecond, a.ActiveTime) + assertEqual(t, 2100*time.Millisecond, a.ActiveTime) } func TestAnalytics_AnalyseLatencyCalculations_Good(t *testing.T) { @@ -183,12 +180,12 @@ func TestAnalytics_AnalyseLatencyCalculations_Good(t *testing.T) { a := Analyse(sess) // Bash: avg = (1+3+5)/3 = 3s, max = 5s - assert.Equal(t, 3*time.Second, a.AvgLatency["Bash"]) - assert.Equal(t, 5*time.Second, a.MaxLatency["Bash"]) + assertEqual(t, 3*time.Second, a.AvgLatency["Bash"]) + assertEqual(t, 5*time.Second, a.MaxLatency["Bash"]) // Read: avg = 200ms, max = 200ms - assert.Equal(t, 200*time.Millisecond, a.AvgLatency["Read"]) - assert.Equal(t, 200*time.Millisecond, a.MaxLatency["Read"]) + assertEqual(t, 200*time.Millisecond, a.AvgLatency["Read"]) + assertEqual(t, 200*time.Millisecond, a.MaxLatency["Read"]) } func TestAnalytics_AnalyseTokenEstimation_Good(t *testing.T) { @@ -220,9 +217,9 @@ func TestAnalytics_AnalyseTokenEstimation_Good(t *testing.T) { a := Analyse(sess) // Input tokens: 400/4 + 80/4 + 120/4 = 100 + 20 + 30 = 150 - assert.Equal(t, 150, a.EstimatedInputTokens) + assertEqual(t, 150, a.EstimatedInputTokens) // Output tokens: 0 + 200/4 + 0 = 50 - assert.Equal(t, 50, a.EstimatedOutputTokens) + assertEqual(t, 50, a.EstimatedOutputTokens) } func TestAnalytics_FormatAnalyticsOutput_Good(t *testing.T) { @@ -255,17 +252,17 @@ func TestAnalytics_FormatAnalyticsOutput_Good(t *testing.T) { output := FormatAnalytics(a) - assert.Contains(t, output, "Session Analytics") - assert.Contains(t, output, "5m0s") - assert.Contains(t, output, "2m0s") - assert.Contains(t, output, "42") - assert.Contains(t, output, "85.0%") - assert.Contains(t, output, "1500") - assert.Contains(t, output, "3000") - assert.Contains(t, output, "Bash") - assert.Contains(t, output, "Read") - assert.Contains(t, output, "Edit") - assert.Contains(t, output, "Tool Breakdown") + assertContains(t, output, "Session Analytics") + assertContains(t, output, "5m0s") + assertContains(t, output, "2m0s") + assertContains(t, output, "42") + assertContains(t, output, "85.0%") + assertContains(t, output, "1500") + assertContains(t, output, "3000") + assertContains(t, output, "Bash") + assertContains(t, output, "Read") + assertContains(t, output, "Edit") + assertContains(t, output, "Tool Breakdown") } func TestAnalytics_FormatAnalyticsEmptyAnalytics_Good(t *testing.T) { @@ -278,8 +275,8 @@ func TestAnalytics_FormatAnalyticsEmptyAnalytics_Good(t *testing.T) { output := FormatAnalytics(a) - assert.Contains(t, output, "Session Analytics") - assert.Contains(t, output, "0.0%") + assertContains(t, output, "Session Analytics") + assertContains(t, output, "0.0%") // No tool breakdown section when no tools - assert.NotContains(t, output, "Tool Breakdown") + assertNotContains(t, output, "Tool Breakdown") } diff --git a/conventions_test.go b/conventions_test.go index f6bbd4e..d292cfc 100644 --- a/conventions_test.go +++ b/conventions_test.go @@ -33,7 +33,7 @@ func TestConventions_BannedImports_Good(t *testing.T) { for _, spec := range file.ast.Imports { importPath := trimQuotes(spec.Path.Value) if core.HasPrefix(importPath, "forge.lthn.ai/") { - t.Errorf("%s imports %q; use dappco.re/go/core/... paths instead", file.path, importPath) + t.Errorf("%s imports %q; use dappco.re/go/... paths instead", file.path, importPath) continue } if reason, ok := banned[importPath]; ok { diff --git a/core_helpers.go b/core_helpers.go index af0040b..886374a 100644 --- a/core_helpers.go +++ b/core_helpers.go @@ -2,7 +2,7 @@ package session import ( - "bytes" + "bytes" // Note: intrinsic — byte-slice helpers implement local string primitives without strings import; no core equivalent core "dappco.re/go/core" ) diff --git a/go.mod b/go.mod index 11183d1..fb10cc3 100644 --- a/go.mod +++ b/go.mod @@ -1,15 +1,5 @@ -module dappco.re/go/core/session +module dappco.re/go/session go 1.26.0 -require ( - dappco.re/go/core v0.8.0-alpha.1 - github.com/stretchr/testify v1.11.1 -) - -require ( - github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect - github.com/kr/text v0.2.0 // indirect - github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect - gopkg.in/yaml.v3 v3.0.1 // indirect -) +require dappco.re/go/core v0.8.0-alpha.1 diff --git a/go.sum b/go.sum index 5092fed..c8d1dca 100644 --- a/go.sum +++ b/go.sum @@ -1,20 +1,8 @@ dappco.re/go/core v0.8.0-alpha.1 h1:gj7+Scv+L63Z7wMxbJYHhaRFkHJo2u4MMPuUSv/Dhtk= dappco.re/go/core v0.8.0-alpha.1/go.mod h1:f2/tBZ3+3IqDrg2F5F598llv0nmb/4gJVCFzM5geE4A= -github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= -github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= -github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= -github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= -github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= -github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= -github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/html.go b/html.go index 431ee5e..44bb358 100644 --- a/html.go +++ b/html.go @@ -2,9 +2,9 @@ package session import ( - "html" - "path" - "time" + "html" // Note: intrinsic — escaping transcript content for generated HTML; stdlib encoder is the output contract + "path" // Note: intrinsic — output parent directory derivation for slash-separated paths; no core equivalent + "time" // Note: intrinsic — duration formatting thresholds for rendered summaries; no core equivalent core "dappco.re/go/core" ) diff --git a/html_test.go b/html_test.go index 1dd9c69..3830c48 100644 --- a/html_test.go +++ b/html_test.go @@ -6,8 +6,6 @@ import ( "time" core "dappco.re/go/core" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" ) func TestHTML_RenderHTMLBasicSession_Good(t *testing.T) { @@ -54,30 +52,30 @@ func TestHTML_RenderHTMLBasicSession_Good(t *testing.T) { } err := RenderHTML(sess, outputPath) - require.NoError(t, err) + requireNoError(t, err) readResult := hostFS.Read(outputPath) - require.True(t, readResult.OK) + requireTrue(t, readResult.OK) html := readResult.Value.(string) // Basic structure checks - assert.Contains(t, html, "") - assert.Contains(t, html, "test-ses") // shortID of "test-session-12345678" - assert.Contains(t, html, "2026-02-20 10:00:00") - assert.Contains(t, html, "5m30s") // duration - assert.Contains(t, html, "2 tool calls") - assert.Contains(t, html, "ls -la") - assert.Contains(t, html, "total 42") - assert.Contains(t, html, "/tmp/file.go") - assert.Contains(t, html, "User") // user event label - assert.Contains(t, html, "Claude") // assistant event label - assert.Contains(t, html, "Bash") - assert.Contains(t, html, "Read") - assert.Contains(t, html, `href="#evt-0"`) - assert.Contains(t, html, "openHashEvent") + assertContains(t, html, "") + assertContains(t, html, "test-ses") // shortID of "test-session-12345678" + assertContains(t, html, "2026-02-20 10:00:00") + assertContains(t, html, "5m30s") // duration + assertContains(t, html, "2 tool calls") + assertContains(t, html, "ls -la") + assertContains(t, html, "total 42") + assertContains(t, html, "/tmp/file.go") + assertContains(t, html, "User") // user event label + assertContains(t, html, "Claude") // assistant event label + assertContains(t, html, "Bash") + assertContains(t, html, "Read") + assertContains(t, html, `href="#evt-0"`) + assertContains(t, html, "openHashEvent") // Should contain JS for toggle and filter - assert.Contains(t, html, "function toggle") - assert.Contains(t, html, "function filterEvents") + assertContains(t, html, "function toggle") + assertContains(t, html, "function filterEvents") } func TestHTML_RenderHTMLEmptySession_Good(t *testing.T) { @@ -93,15 +91,15 @@ func TestHTML_RenderHTMLEmptySession_Good(t *testing.T) { } err := RenderHTML(sess, outputPath) - require.NoError(t, err) + requireNoError(t, err) readResult := hostFS.Read(outputPath) - require.True(t, readResult.OK) + requireTrue(t, readResult.OK) html := readResult.Value.(string) - assert.Contains(t, html, "") - assert.Contains(t, html, "0 tool calls") + assertContains(t, html, "") + assertContains(t, html, "0 tool calls") // Should NOT contain error span - assert.NotContains(t, html, "errors") + assertNotContains(t, html, "errors") } func TestHTML_RenderHTMLWithErrors_Good(t *testing.T) { @@ -137,15 +135,15 @@ func TestHTML_RenderHTMLWithErrors_Good(t *testing.T) { } err := RenderHTML(sess, outputPath) - require.NoError(t, err) + requireNoError(t, err) readResult := hostFS.Read(outputPath) - require.True(t, readResult.OK) + requireTrue(t, readResult.OK) html := readResult.Value.(string) - assert.Contains(t, html, "1 errors") - assert.Contains(t, html, `class="event error"`) - assert.Contains(t, html, "✗") // cross mark for failed - assert.Contains(t, html, "✓") // check mark for success + assertContains(t, html, "1 errors") + assertContains(t, html, `class="event error"`) + assertContains(t, html, "✗") // cross mark for failed + assertContains(t, html, "✓") // check mark for success } func TestHTML_RenderHTMLSpecialCharacters_Good(t *testing.T) { @@ -176,16 +174,16 @@ func TestHTML_RenderHTMLSpecialCharacters_Good(t *testing.T) { } err := RenderHTML(sess, outputPath) - require.NoError(t, err) + requireNoError(t, err) readResult := hostFS.Read(outputPath) - require.True(t, readResult.OK) + requireTrue(t, readResult.OK) html := readResult.Value.(string) // Script tags should be escaped, never raw - assert.NotContains(t, html, "