diff --git a/.golangci.yml b/.golangci.yml index 15d27968..5c05d899 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -95,9 +95,11 @@ linters: path: _test\.go text: "G204|G704" - # goconst: repeated test fixture strings + # goconst: test-fixture strings (entity/document/category names, MIME + # types, SQLite type names, column-name map keys, ULIDs, dates) + # legitimately repeat across test cases - linters: [goconst] - path: internal/relay/(handler_suite|pgstore|stripe)_test\.go + path: _test\.go # unqueryvet: SELECT * in test SQL fixtures is intentional - linters: [unqueryvet] diff --git a/go.mod b/go.mod index 45072a48..639c8eb1 100644 --- a/go.mod +++ b/go.mod @@ -15,30 +15,30 @@ require ( github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 github.com/iancoleman/strcase v0.3.0 github.com/itchyny/gojq v0.12.19 - github.com/mark3labs/mcp-go v0.49.0 + github.com/mark3labs/mcp-go v0.52.0 github.com/mozilla-ai/any-llm-go v0.9.0 - github.com/nyaruka/phonenumbers v1.7.1 + github.com/nyaruka/phonenumbers v1.7.4 github.com/oklog/ulid/v2 v2.1.1 github.com/spf13/cobra v1.10.2 github.com/spf13/pflag v1.0.10 github.com/stretchr/testify v1.11.1 github.com/tj/go-naturaldate v1.3.0 - golang.org/x/crypto v0.50.0 - golang.org/x/sys v0.43.0 - golang.org/x/term v0.42.0 - golang.org/x/text v0.36.0 + golang.org/x/crypto v0.51.0 + golang.org/x/sys v0.44.0 + golang.org/x/term v0.43.0 + golang.org/x/text v0.37.0 gorm.io/driver/postgres v1.6.0 gorm.io/gorm v1.31.1 - modernc.org/sqlite v1.50.0 + modernc.org/sqlite v1.50.1 ) require ( - github.com/charmbracelet/ultraviolet v0.0.0-20260422141423-a0f1f21775f7 // indirect - github.com/charmbracelet/x/exp/charmtone v0.0.0-20260427100455-1ea3e7f8134f // indirect + github.com/charmbracelet/ultraviolet v0.0.0-20260511121909-c840852527f3 // indirect + github.com/charmbracelet/x/exp/charmtone v0.0.0-20260511125431-fe5d686e0c99 // indirect github.com/charmbracelet/x/exp/ordered v0.1.0 // indirect github.com/charmbracelet/x/termios v0.1.1 // indirect github.com/charmbracelet/x/windows v0.2.2 // indirect - github.com/google/jsonschema-go v0.4.2 // indirect + github.com/google/jsonschema-go v0.4.3 // indirect github.com/invopop/jsonschema v0.13.0 // indirect github.com/jackc/pgpassfile v1.0.0 // indirect github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect @@ -48,10 +48,11 @@ require ( github.com/muesli/mango-cobra v1.3.0 // indirect github.com/muesli/mango-pflag v0.2.0 // indirect github.com/muesli/roff v0.1.0 // indirect + github.com/santhosh-tekuri/jsonschema/v6 v6.0.2 // indirect github.com/spf13/cast v1.10.0 // indirect + github.com/standard-webhooks/standard-webhooks/libraries v0.0.1 // indirect github.com/yosida95/uritemplate/v3 v3.0.2 // indirect - golang.org/x/tools v0.44.0 // indirect - google.golang.org/api v0.276.0 // indirect + google.golang.org/api v0.278.0 // indirect ) require ( @@ -63,18 +64,18 @@ require ( cloud.google.com/go v0.123.0 // indirect cloud.google.com/go/auth v0.20.0 // indirect cloud.google.com/go/compute/metadata v0.9.0 // indirect - github.com/alecthomas/chroma/v2 v2.23.1 // indirect - github.com/anthropics/anthropic-sdk-go v1.38.0 // indirect + github.com/alecthomas/chroma/v2 v2.24.1 // indirect + github.com/anthropics/anthropic-sdk-go v1.42.0 // indirect github.com/atotto/clipboard v0.1.4 // indirect github.com/aymerick/douceur v0.2.0 // indirect github.com/bahlo/generic-list-go v0.2.0 // indirect - github.com/buger/jsonparser v1.1.2 // indirect + github.com/buger/jsonparser v1.2.0 // indirect github.com/catppuccin/go v0.3.0 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/charmbracelet/colorprofile v0.4.3 // indirect github.com/charmbracelet/harmonica v0.2.0 // indirect github.com/charmbracelet/x/ansi v0.11.7 - github.com/charmbracelet/x/exp/slice v0.0.0-20260427100455-1ea3e7f8134f // indirect + github.com/charmbracelet/x/exp/slice v0.0.0-20260511125431-fe5d686e0c99 // indirect github.com/charmbracelet/x/exp/strings v0.1.0 // indirect github.com/charmbracelet/x/term v0.2.2 github.com/clipperhouse/displaywidth v0.11.0 // indirect @@ -108,12 +109,12 @@ require ( github.com/mitchellh/hashstructure/v2 v2.0.2 // indirect github.com/muesli/cancelreader v0.2.2 // indirect github.com/ncruces/go-strftime v1.0.0 // indirect - github.com/ollama/ollama v0.21.2 // indirect + github.com/ollama/ollama v0.23.0 // indirect github.com/openai/openai-go v1.12.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect github.com/rivo/uniseg v0.4.7 - github.com/tidwall/gjson v1.18.0 // indirect + github.com/tidwall/gjson v1.19.0 // indirect github.com/tidwall/match v1.2.0 // indirect github.com/tidwall/pretty v1.2.1 // indirect github.com/tidwall/sjson v1.2.5 // indirect @@ -127,14 +128,14 @@ require ( go.opentelemetry.io/otel/metric v1.43.0 // indirect go.opentelemetry.io/otel/trace v1.43.0 // indirect golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f // indirect - golang.org/x/net v0.53.0 // indirect + golang.org/x/net v0.54.0 // indirect golang.org/x/sync v0.20.0 // indirect - google.golang.org/genai v1.54.0 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20260420184626-e10c466a9529 // indirect - google.golang.org/grpc v1.80.0 // indirect + google.golang.org/genai v1.56.0 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260511170946-3700d4141b60 // indirect + google.golang.org/grpc v1.81.0 // indirect google.golang.org/protobuf v1.36.11 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - modernc.org/libc v1.72.0 // indirect + modernc.org/libc v1.72.3 // indirect modernc.org/mathutil v1.7.1 // indirect modernc.org/memory v1.11.0 // indirect ) diff --git a/go.sum b/go.sum index 1d37d63d..e710e1f1 100644 --- a/go.sum +++ b/go.sum @@ -24,12 +24,12 @@ github.com/adrg/xdg v0.5.3 h1:xRnxJXne7+oWDatRhR1JLnvuccuIeCoBu2rtuLqQB78= github.com/adrg/xdg v0.5.3/go.mod h1:nlTsY+NNiCBGCK2tpm09vRqfVzrc2fLmXGpBLF0zlTQ= github.com/alecthomas/assert/v2 v2.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0= github.com/alecthomas/assert/v2 v2.11.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k= -github.com/alecthomas/chroma/v2 v2.23.1 h1:nv2AVZdTyClGbVQkIzlDm/rnhk1E9bU9nXwmZ/Vk/iY= -github.com/alecthomas/chroma/v2 v2.23.1/go.mod h1:NqVhfBR0lte5Ouh3DcthuUCTUpDC9cxBOfyMbMQPs3o= +github.com/alecthomas/chroma/v2 v2.24.1 h1:m5ffpfZbIb++k8AqFEKy9uVgY12xIQtBsQlc6DfZJQM= +github.com/alecthomas/chroma/v2 v2.24.1/go.mod h1:l+ohZ9xRXIbGe7cIW+YZgOGbvuVLjMps/FYN/CwuabI= github.com/alecthomas/repr v0.5.2 h1:SU73FTI9D1P5UNtvseffFSGmdNci/O6RsqzeXJtP0Qs= github.com/alecthomas/repr v0.5.2/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4= -github.com/anthropics/anthropic-sdk-go v1.38.0 h1:bA4DcK+91gorIX+5VTONnynyt9LRU4nnN6rRQ+j/NIg= -github.com/anthropics/anthropic-sdk-go v1.38.0/go.mod h1:d288C1L+m74OYuYBvc4UFtR1Q8J0gC55oYDh2t+XxdI= +github.com/anthropics/anthropic-sdk-go v1.42.0 h1:Zv882/dnrE4OHnwhMAsi9lwVVXRF8GtR3ofiBResYUw= +github.com/anthropics/anthropic-sdk-go v1.42.0/go.mod h1:r4eaLX9tBolUrXLOrLj7eU8tmeBtoobCkM0kBsivBaY= github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4= github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI= github.com/aymanbagabas/go-udiff v0.4.1 h1:OEIrQ8maEeDBXQDoGCbbTTXYJMYRCRO1fnodZ12Gv5o= @@ -40,8 +40,8 @@ github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPn github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg= github.com/brianvoe/gofakeit/v7 v7.14.1 h1:a7fe3fonbj0cW3wgl5VwIKfZtiH9C3cLnwcIXWT7sow= github.com/brianvoe/gofakeit/v7 v7.14.1/go.mod h1:QXuPeBw164PJCzCUZVmgpgHJ3Llj49jSLVkKPMtxtxA= -github.com/buger/jsonparser v1.1.2 h1:frqHqw7otoVbk5M8LlE/L7HTnIq2v9RX6EJ48i9AxJk= -github.com/buger/jsonparser v1.1.2/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0= +github.com/buger/jsonparser v1.2.0 h1:4EFcvK1kD4jyj6YqNK6skK6w+y7FHHBR+XBCtxwu/6g= +github.com/buger/jsonparser v1.2.0/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0= github.com/catppuccin/go v0.3.0 h1:d+0/YicIq+hSTo5oPuRi5kOpqkVA5tAsU6dNhvRu+aY= github.com/catppuccin/go v0.3.0/go.mod h1:8IHJuMGaUUjQM82qBrGNBv7LFq6JI3NnQCF6MOlZjpc= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= @@ -50,22 +50,22 @@ github.com/charmbracelet/colorprofile v0.4.3 h1:QPa1IWkYI+AOB+fE+mg/5/4HRMZcaXex github.com/charmbracelet/colorprofile v0.4.3/go.mod h1:/zT4BhpD5aGFpqQQqw7a+VtHCzu+zrQtt1zhMt9mR4Q= github.com/charmbracelet/harmonica v0.2.0 h1:8NxJWRWg/bzKqqEaaeFNipOu77YR5t8aSwG4pgaUBiQ= github.com/charmbracelet/harmonica v0.2.0/go.mod h1:KSri/1RMQOZLbw7AHqgcBycp8pgJnQMYYT8QZRqZ1Ao= -github.com/charmbracelet/ultraviolet v0.0.0-20260422141423-a0f1f21775f7 h1:PeRlqWGEoO0apcS62iEgxQhVnFCTOYyQvi2sUTdf6IE= -github.com/charmbracelet/ultraviolet v0.0.0-20260422141423-a0f1f21775f7/go.mod h1:3YdTxlnV/L0bQ3VN8WOSw8doF7LZV/xawUQ4MuAPDvo= +github.com/charmbracelet/ultraviolet v0.0.0-20260511121909-c840852527f3 h1:pxGjlWZFcRQMWAdtjRelpL3Gbu8iYIyuO3Eqbd037Ow= +github.com/charmbracelet/ultraviolet v0.0.0-20260511121909-c840852527f3/go.mod h1:SnKWaPaTnkTNXJgdgdquu66de12V8pW/b/qlTGaF9xg= github.com/charmbracelet/x/ansi v0.11.7 h1:kzv1kJvjg2S3r9KHo8hDdHFQLEqn4RBCb39dAYC84jI= github.com/charmbracelet/x/ansi v0.11.7/go.mod h1:9qGpnAVYz+8ACONkZBUWPtL7lulP9No6p1epAihUZwQ= github.com/charmbracelet/x/conpty v0.1.1 h1:s1bUxjoi7EpqiXysVtC+a8RrvPPNcNvAjfi4jxsAuEs= github.com/charmbracelet/x/conpty v0.1.1/go.mod h1:OmtR77VODEFbiTzGE9G1XiRJAga6011PIm4u5fTNZpk= github.com/charmbracelet/x/errors v0.0.0-20240508181413-e8d8b6e2de86 h1:JSt3B+U9iqk37QUU2Rvb6DSBYRLtWqFqfxf8l5hOZUA= github.com/charmbracelet/x/errors v0.0.0-20240508181413-e8d8b6e2de86/go.mod h1:2P0UgXMEa6TsToMSuFqKFQR+fZTO9CNGUNokkPatT/0= -github.com/charmbracelet/x/exp/charmtone v0.0.0-20260427100455-1ea3e7f8134f h1:/GBGrUmL/wvbobBMw5E4ugM6DG36PZB+Vaq5BmKulAg= -github.com/charmbracelet/x/exp/charmtone v0.0.0-20260427100455-1ea3e7f8134f/go.mod h1:nsExn0DGyX0lh9LwLHTn2Gg+hafdzfSXnC+QmEJTZFY= +github.com/charmbracelet/x/exp/charmtone v0.0.0-20260511125431-fe5d686e0c99 h1:79Whx3H/thq9X9I+iqsi7o/pVaI7EhaIWbzB173eHsw= +github.com/charmbracelet/x/exp/charmtone v0.0.0-20260511125431-fe5d686e0c99/go.mod h1:nsExn0DGyX0lh9LwLHTn2Gg+hafdzfSXnC+QmEJTZFY= github.com/charmbracelet/x/exp/golden v0.0.0-20250806222409-83e3a29d542f h1:pk6gmGpCE7F3FcjaOEKYriCvpmIN4+6OS/RD0vm4uIA= github.com/charmbracelet/x/exp/golden v0.0.0-20250806222409-83e3a29d542f/go.mod h1:IfZAMTHB6XkZSeXUqriemErjAWCCzT0LwjKFYCZyw0I= github.com/charmbracelet/x/exp/ordered v0.1.0 h1:55/qLwjIh0gL0Vni+QAWk7T/qRVP6sBf+2agPBgnOFE= github.com/charmbracelet/x/exp/ordered v0.1.0/go.mod h1:5UHwmG+is5THxMyCJHNPCn2/ecI07aKNrW+LcResjJ8= -github.com/charmbracelet/x/exp/slice v0.0.0-20260427100455-1ea3e7f8134f h1:oOjh6v/BYfyvtpVnXKxmjsYnVs62hmWCwNM8wfkGr/M= -github.com/charmbracelet/x/exp/slice v0.0.0-20260427100455-1ea3e7f8134f/go.mod h1:vqEfX6xzqW1pKKZUUiFOKg0OQ7bCh54Q2vR/tserrRA= +github.com/charmbracelet/x/exp/slice v0.0.0-20260511125431-fe5d686e0c99 h1:e4VttUIAVgO4neqnJG80U4BE//1kcvyOrJ5utftPXQE= +github.com/charmbracelet/x/exp/slice v0.0.0-20260511125431-fe5d686e0c99/go.mod h1:vqEfX6xzqW1pKKZUUiFOKg0OQ7bCh54Q2vR/tserrRA= github.com/charmbracelet/x/exp/strings v0.1.0 h1:i69S2XI7uG1u4NLGeJPSYU++Nmjvpo9nwd6aoEm7gkA= github.com/charmbracelet/x/exp/strings v0.1.0/go.mod h1:/ehtMPNh9K4odGFkqYJKpIYyePhdp1hLBRvyY4bWkH8= github.com/charmbracelet/x/term v0.2.2 h1:xVRT/S2ZcKdhhOuSP4t5cLi5o+JxklsoEObBSgfgZRk= @@ -115,8 +115,8 @@ github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= -github.com/google/jsonschema-go v0.4.2 h1:tmrUohrwoLZZS/P3x7ex0WAVknEkBZM46iALbcqoRA8= -github.com/google/jsonschema-go v0.4.2/go.mod h1:r5quNTdLOYEz95Ru18zA0ydNbBuYoo9tgaYcxEYhJVE= +github.com/google/jsonschema-go v0.4.3 h1:/DBOLZTfDow7pe2GmaJNhltueGTtDKICi8V8p+DQPd0= +github.com/google/jsonschema-go v0.4.3/go.mod h1:r5quNTdLOYEz95Ru18zA0ydNbBuYoo9tgaYcxEYhJVE= github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e h1:ijClszYn+mADRFY17kjQEVQ1XRhq2/JR1M3sGqeJoxs= github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA= github.com/google/s2a-go v0.1.9 h1:LGD7gtMgezd8a/Xak7mEWL0PjoTQFvpRudN895yqKW0= @@ -171,8 +171,8 @@ github.com/lucasb-eyer/go-colorful v1.4.0 h1:UtrWVfLdarDgc44HcS7pYloGHJUjHV/4FwW github.com/lucasb-eyer/go-colorful v1.4.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= github.com/mailru/easyjson v0.9.2 h1:dX8U45hQsZpxd80nLvDGihsQ/OxlvTkVUXH2r/8cb2M= github.com/mailru/easyjson v0.9.2/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU= -github.com/mark3labs/mcp-go v0.49.0 h1:7Ssx4d7/T86qnWoJIdye7wEEvUzv39UIbnZb/FqUZMY= -github.com/mark3labs/mcp-go v0.49.0/go.mod h1:BflTAZAzXlrTpiO44gmjMu89n2FO56rJ9m31fp4zd5k= +github.com/mark3labs/mcp-go v0.52.0 h1:uRSzupNSUyPGDpF4owY5X4zEpACPwBnlM3FAFuXN6gQ= +github.com/mark3labs/mcp-go v0.52.0/go.mod h1:Zg9cB2HdwdMMVgY0xtTzq3KvYIOJQDsaut+jWjwDaQY= github.com/mattn/go-isatty v0.0.22 h1:j8l17JJ9i6VGPUFUYoTUKPSgKe/83EYU2zBC7YNKMw4= github.com/mattn/go-isatty v0.0.22/go.mod h1:ZXfXG4SQHsB/w3ZeOYbR0PrPwLy+n6xiMrJlRFqopa4= github.com/mattn/go-runewidth v0.0.23 h1:7ykA0T0jkPpzSvMS5i9uoNn2Xy3R383f9HDx3RybWcw= @@ -195,12 +195,12 @@ github.com/muesli/roff v0.1.0 h1:YD0lalCotmYuF5HhZliKWlIx7IEhiXeSfq7hNjFqGF8= github.com/muesli/roff v0.1.0/go.mod h1:pjAHQM9hdUUwm/krAfrLGgJkXJ+YuhtsfZ42kieB2Ig= github.com/ncruces/go-strftime v1.0.0 h1:HMFp8mLCTPp341M/ZnA4qaf7ZlsbTc+miZjCLOFAw7w= github.com/ncruces/go-strftime v1.0.0/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls= -github.com/nyaruka/phonenumbers v1.7.1 h1:k8FHBMLegwW2tEIhsurC5YJk5Dix++H1k6liu1LUruY= -github.com/nyaruka/phonenumbers v1.7.1/go.mod h1:fsKPJ70O9JetEA4ggnJadYTFWwtGPvu/lETTXNXq6Cs= +github.com/nyaruka/phonenumbers v1.7.4 h1:6gHEciDijqfY1EHiZTQLidEB2lsvU0rFvOL08EoIrhg= +github.com/nyaruka/phonenumbers v1.7.4/go.mod h1:fsKPJ70O9JetEA4ggnJadYTFWwtGPvu/lETTXNXq6Cs= github.com/oklog/ulid/v2 v2.1.1 h1:suPZ4ARWLOJLegGFiZZ1dFAkqzhMjL3J1TzI+5wHz8s= github.com/oklog/ulid/v2 v2.1.1/go.mod h1:rcEKHmBBKfef9DhnvX7y1HZBYxjXb0cP5ExxNsTT1QQ= -github.com/ollama/ollama v0.21.2 h1:CDChYRau12aj6XFt/N0xOKudrV/1zhUad7cKlZetDY0= -github.com/ollama/ollama v0.21.2/go.mod h1:274niu48upWz/M7vL53i1WFe+TJRRw5oo4GiacbIYrA= +github.com/ollama/ollama v0.23.0 h1:13V15B9Pkwl+WAaaU90NW6vik54dYgPhVrdjtw+dqck= +github.com/ollama/ollama v0.23.0/go.mod h1:274niu48upWz/M7vL53i1WFe+TJRRw5oo4GiacbIYrA= github.com/openai/openai-go v1.12.0 h1:NBQCnXzqOTv5wsgNC36PrFEiskGfO5wccfCWDo9S1U0= github.com/openai/openai-go v1.12.0/go.mod h1:g461MYGXEXBVdV5SaR/5tNzNbSfwTBBefwc+LlDCK0Y= github.com/pborman/getopt v0.0.0-20170112200414-7148bc3a4c30/go.mod h1:85jBQOZwpVEaDAr341tbn15RS4fCAsIst0qp7i8ex1o= @@ -213,6 +213,8 @@ github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUc 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/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/santhosh-tekuri/jsonschema/v6 v6.0.2 h1:KRzFb2m7YtdldCEkzs6KqmJw4nqEVZGK7IN2kJkjTuQ= +github.com/santhosh-tekuri/jsonschema/v6 v6.0.2/go.mod h1:JXeL+ps8p7/KNMjDQk3TCwPpBy0wYklyWTfbkIzdIFU= github.com/spf13/cast v1.10.0 h1:h2x0u2shc1QuLHfxi+cTJvs30+ZAHOGRic8uyGTDWxY= github.com/spf13/cast v1.10.0/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo= github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU= @@ -220,6 +222,8 @@ github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiT github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk= github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/standard-webhooks/standard-webhooks/libraries v0.0.1 h1:uOfcYT+3QungH6tIGSVCR/Y3KJmgJiHcojJbMTPDZAI= +github.com/standard-webhooks/standard-webhooks/libraries v0.0.1/go.mod h1:L1MQhA6x4dn9r007T033lsaZMv9EmBAdXyU/+EF40fo= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= @@ -227,8 +231,8 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= -github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY= -github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/gjson v1.19.0 h1:xwxm7n691Uf3u5OFjzngavjGTh55KX5q/9w9xHW88JU= +github.com/tidwall/gjson v1.19.0/go.mod h1:V37/opeE/JbLUOfH0QTXiNez2l0RUjYUhpT4szFQAfc= github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= github.com/tidwall/match v1.2.0 h1:0pt8FlkOwjN2fPt4bIl4BoNxb98gGHN2ObFEDkrfZnM= github.com/tidwall/match v1.2.0/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= @@ -266,38 +270,38 @@ go.opentelemetry.io/otel/sdk/metric v1.43.0/go.mod h1:C/RJtwSEJ5hzTiUz5pXF1kILHS go.opentelemetry.io/otel/trace v1.43.0 h1:BkNrHpup+4k4w+ZZ86CZoHHEkohws8AY+WTX09nk+3A= go.opentelemetry.io/otel/trace v1.43.0/go.mod h1:/QJhyVBUUswCphDVxq+8mld+AvhXZLhe+8WVFxiFff0= go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= -golang.org/x/crypto v0.50.0 h1:zO47/JPrL6vsNkINmLoo/PH1gcxpls50DNogFvB5ZGI= -golang.org/x/crypto v0.50.0/go.mod h1:3muZ7vA7PBCE6xgPX7nkzzjiUq87kRItoJQM1Yo8S+Q= +golang.org/x/crypto v0.51.0 h1:IBPXwPfKxY7cWQZ38ZCIRPI50YLeevDLlLnyC5wRGTI= +golang.org/x/crypto v0.51.0/go.mod h1:8AdwkbraGNABw2kOX6YFPs3WM22XqI4EXEd8g+x7Oc8= golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f h1:W3F4c+6OLc6H2lb//N1q4WpJkhzJCK5J6kUi1NTVXfM= golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f/go.mod h1:J1xhfL/vlindoeF/aINzNzt2Bket5bjo9sdOYzOsU80= golang.org/x/mod v0.35.0 h1:Ww1D637e6Pg+Zb2KrWfHQUnH2dQRLBQyAtpr/haaJeM= golang.org/x/mod v0.35.0/go.mod h1:+GwiRhIInF8wPm+4AoT6L0FA1QWAad3OMdTRx4tFYlU= -golang.org/x/net v0.53.0 h1:d+qAbo5L0orcWAr0a9JweQpjXF19LMXJE8Ey7hwOdUA= -golang.org/x/net v0.53.0/go.mod h1:JvMuJH7rrdiCfbeHoo3fCQU24Lf5JJwT9W3sJFulfgs= +golang.org/x/net v0.54.0 h1:2zJIZAxAHV/OHCDTCOHAYehQzLfSXuf/5SoL/Dv6w/w= +golang.org/x/net v0.54.0/go.mod h1:Sj4oj8jK6XmHpBZU/zWHw3BV3abl4Kvi+Ut7cQcY+cQ= golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4= golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= -golang.org/x/sys v0.43.0 h1:Rlag2XtaFTxp19wS8MXlJwTvoh8ArU6ezoyFsMyCTNI= -golang.org/x/sys v0.43.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= -golang.org/x/term v0.42.0 h1:UiKe+zDFmJobeJ5ggPwOshJIVt6/Ft0rcfrXZDLWAWY= -golang.org/x/term v0.42.0/go.mod h1:Dq/D+snpsbazcBG5+F9Q1n2rXV8Ma+71xEjTRufARgY= -golang.org/x/text v0.36.0 h1:JfKh3XmcRPqZPKevfXVpI1wXPTqbkE5f7JA92a55Yxg= -golang.org/x/text v0.36.0/go.mod h1:NIdBknypM8iqVmPiuco0Dh6P5Jcdk8lJL0CUebqK164= +golang.org/x/sys v0.44.0 h1:ildZl3J4uzeKP07r2F++Op7E9B29JRUy+a27EibtBTQ= +golang.org/x/sys v0.44.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/term v0.43.0 h1:S4RLU2sB31O/NCl+zFN9Aru9A/Cq2aqKpTZJ6B+DwT4= +golang.org/x/term v0.43.0/go.mod h1:lrhlHNdQJHO+1qVYiHfFKVuVioJIheAc3fBSMFYEIsk= +golang.org/x/text v0.37.0 h1:Cqjiwd9eSg8e0QAkyCaQTNHFIIzWtidPahFWR83rTrc= +golang.org/x/text v0.37.0/go.mod h1:a5sjxXGs9hsn/AJVwuElvCAo9v8QYLzvavO5z2PiM38= golang.org/x/tools v0.44.0 h1:UP4ajHPIcuMjT1GqzDWRlalUEoY+uzoZKnhOjbIPD2c= golang.org/x/tools v0.44.0/go.mod h1:KA0AfVErSdxRZIsOVipbv3rQhVXTnlU6UhKxHd1seDI= gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4= gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E= -google.golang.org/api v0.276.0 h1:nVArUtfLEihtW+b0DdcqRGK1xoEm2+ltAihyztq7MKY= -google.golang.org/api v0.276.0/go.mod h1:Fnag/EWUPIcJXuIkP1pjoTgS5vdxlk3eeemL7Do6bvw= -google.golang.org/genai v1.54.0 h1:ZQCa70WMTJDI11FdqWCzGvZ5PanpcpfoO6jl/lrSnGU= -google.golang.org/genai v1.54.0/go.mod h1:A3kkl0nyBjyFlNjgxIwKq70julKbIxpSxqKO5gw/gmk= +google.golang.org/api v0.278.0 h1:W7jiRvRi53VYFfZ/HoZjQBtJk7gOFbHD8ot1RzVZU6E= +google.golang.org/api v0.278.0/go.mod h1:B9TqLBwJqVjp1mtt7WeoQwWRwvu/400y5lETOql+giQ= +google.golang.org/genai v1.56.0 h1:IwWrg1K0cn1/WBiPno/dYr0Q6o75NeH/bh3G4JEFERE= +google.golang.org/genai v1.56.0/go.mod h1:A3kkl0nyBjyFlNjgxIwKq70julKbIxpSxqKO5gw/gmk= google.golang.org/genproto v0.0.0-20260319201613-d00831a3d3e7 h1:XzmzkmB14QhVhgnawEVsOn6OFsnpyxNPRY9QV01dNB0= google.golang.org/genproto v0.0.0-20260319201613-d00831a3d3e7/go.mod h1:L43LFes82YgSonw6iTXTxXUX1OlULt4AQtkik4ULL/I= google.golang.org/genproto/googleapis/api v0.0.0-20260319201613-d00831a3d3e7 h1:41r6JMbpzBMen0R/4TZeeAmGXSJC7DftGINUodzTkPI= google.golang.org/genproto/googleapis/api v0.0.0-20260319201613-d00831a3d3e7/go.mod h1:EIQZ5bFCfRQDV4MhRle7+OgjNtZ6P1PiZBgAKuxXu/Y= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260420184626-e10c466a9529 h1:XF8+t6QQiS0o9ArVan/HW8Q7cycNPGsJf6GA2nXxYAg= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260420184626-e10c466a9529/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= -google.golang.org/grpc v1.80.0 h1:Xr6m2WmWZLETvUNvIUmeD5OAagMw3FiKmMlTdViWsHM= -google.golang.org/grpc v1.80.0/go.mod h1:ho/dLnxwi3EDJA4Zghp7k2Ec1+c2jqup0bFkw07bwF4= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260511170946-3700d4141b60 h1:seT2EwLWM78plQ7wcDfuWBc/4FAEAXDDiaSol4ku4qo= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260511170946-3700d4141b60/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= +google.golang.org/grpc v1.81.0 h1:W3G9N3KQf3BU+YuCtGKJk0CmxQNbAISICD/9AORxLIw= +google.golang.org/grpc v1.81.0/go.mod h1:xGH9GfzOyMTGIOXBJmXt+BX/V0kcdQbdcuwQ/zNw42I= google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -313,10 +317,10 @@ gorm.io/driver/postgres v1.6.0 h1:2dxzU8xJ+ivvqTRph34QX+WrRaJlmfyPqXmoGVjMBa4= gorm.io/driver/postgres v1.6.0/go.mod h1:vUw0mrGgrTK+uPHEhAdV4sfFELrByKVGnaVRkXDhtWo= gorm.io/gorm v1.31.1 h1:7CA8FTFz/gRfgqgpeKIBcervUn3xSyPUmr6B2WXJ7kg= gorm.io/gorm v1.31.1/go.mod h1:XyQVbO2k6YkOis7C2437jSit3SsDK72s7n7rsSHd+Gs= -modernc.org/cc/v4 v4.27.3 h1:uNCgn37E5U09mTv1XgskEVUJ8ADKpmFMPxzGJ0TSo+U= -modernc.org/cc/v4 v4.27.3/go.mod h1:3YjcbCqhoTTHPycJDRl2WZKKFj0nwcOIPBfEZK0Hdk8= -modernc.org/ccgo/v4 v4.32.4 h1:L5OB8rpEX4ZsXEQwGozRfJyJSFHbbNVOoQ59DU9/KuU= -modernc.org/ccgo/v4 v4.32.4/go.mod h1:lY7f+fiTDHfcv6YlRgSkxYfhs+UvOEEzj49jAn2TOx0= +modernc.org/cc/v4 v4.28.2 h1:3tQ0lf2ADtoby2EtSP+J7IE2SHwEJdP8ioR59wx7XpY= +modernc.org/cc/v4 v4.28.2/go.mod h1:OnovgIhbbMXMu1aISnJ0wvVD1KnW+cAUJkIrAWh+kVI= +modernc.org/ccgo/v4 v4.34.0 h1:yRLPFZieg532OT4rp4JFNIVcquwalMX26G95WQDqwCQ= +modernc.org/ccgo/v4 v4.34.0/go.mod h1:AS5WYMyBakQ+fhsHhtP8mWB82KTGPkNNJDGfGQCe0/A= modernc.org/fileutil v1.4.0 h1:j6ZzNTftVS054gi281TyLjHPp6CPHr2KCxEXjEbD6SM= modernc.org/fileutil v1.4.0/go.mod h1:EqdKFDxiByqxLk8ozOxObDSfcVOv/54xDs/DUHdvCUU= modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI= @@ -325,18 +329,18 @@ modernc.org/gc/v3 v3.1.2 h1:ZtDCnhonXSZexk/AYsegNRV1lJGgaNZJuKjJSWKyEqo= modernc.org/gc/v3 v3.1.2/go.mod h1:HFK/6AGESC7Ex+EZJhJ2Gni6cTaYpSMmU/cT9RmlfYY= modernc.org/goabi0 v0.2.0 h1:HvEowk7LxcPd0eq6mVOAEMai46V+i7Jrj13t4AzuNks= modernc.org/goabi0 v0.2.0/go.mod h1:CEFRnnJhKvWT1c1JTI3Avm+tgOWbkOu5oPA8eH8LnMI= -modernc.org/libc v1.72.0 h1:IEu559v9a0XWjw0DPoVKtXpO2qt5NVLAnFaBbjq+n8c= -modernc.org/libc v1.72.0/go.mod h1:tTU8DL8A+XLVkEY3x5E/tO7s2Q/q42EtnNWda/L5QhQ= +modernc.org/libc v1.72.3 h1:ZnDF4tXn4NBXFutMMQC4vtbTFSXhhKzR73fv0beZEAU= +modernc.org/libc v1.72.3/go.mod h1:dn0dZNnnn1clLyvRxLxYExxiKRZIRENOfqQ8XEeg4Qs= modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU= modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg= modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI= modernc.org/memory v1.11.0/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw= -modernc.org/opt v0.1.4 h1:2kNGMRiUjrp4LcaPuLY2PzUfqM/w9N23quVwhKt5Qm8= -modernc.org/opt v0.1.4/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns= +modernc.org/opt v0.2.0 h1:tGyef5ApycA7FSEOMraay9SaTk5zmbx7Tu+cJs4QKZg= +modernc.org/opt v0.2.0/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns= modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w= modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE= -modernc.org/sqlite v1.50.0 h1:eMowQSWLK0MeiQTdmz3lqoF5dqclujdlIKeJA11+7oM= -modernc.org/sqlite v1.50.0/go.mod h1:m0w8xhwYUVY3H6pSDwc3gkJ/irZT/0YEXwBlhaxQEew= +modernc.org/sqlite v1.50.1 h1:l+cQvn0sd0zJJtfygGHuQJ5AjlrwXmWPw4KP3ZMwr9w= +modernc.org/sqlite v1.50.1/go.mod h1:tcNzv5p84E0skkmJn038y+hWJbLQXQqEnQfeh5r2JLM= modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0= modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A= modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= diff --git a/internal/app/chat.go b/internal/app/chat.go index e69fcef1..91258eea 100644 --- a/internal/app/chat.go +++ b/internal/app/chat.go @@ -364,7 +364,6 @@ func (m *Model) startSQLStream(query string) tea.Cmd { messages = append(messages, history...) messages = append(messages, llm.Message{Role: roleUser, Content: query}) - //nolint:gosec // cancel stored in CancelFn, called on ctrl+c ctx, cancel := context.WithTimeout( appCtx, chatTimeout, ) diff --git a/internal/app/extraction.go b/internal/app/extraction.go index a1dbcedd..d74905d1 100644 --- a/internal/app/extraction.go +++ b/internal/app/extraction.go @@ -280,7 +280,6 @@ func (m *Model) startExtractionOverlay( sp := spinner.New(spinner.WithSpinner(spinner.Dot)) sp.Style = appStyles.AccentText() - //nolint:gosec // cancel stored in ex.CancelFn, called on extraction close ctx, cancel := context.WithCancel( m.lifecycleCtx(), ) @@ -998,7 +997,7 @@ func (m *Model) rerunLLMExtraction() tea.Cmd { // Replace a cancelled context so the rerun has a live one. if ex.ctx.Err() != nil { - ctx, cancel := context.WithCancel( //nolint:gosec // cancel stored in ex.CancelFn, called on extraction close + ctx, cancel := context.WithCancel( m.lifecycleCtx(), ) ex.ctx = ctx diff --git a/internal/app/form_filepicker.go b/internal/app/form_filepicker.go index c734c24b..181980f3 100644 --- a/internal/app/form_filepicker.go +++ b/internal/app/form_filepicker.go @@ -29,7 +29,7 @@ func filePickerDesc(showHidden bool) string { // via reflection. Returns false if the field is not a FilePicker. func filePickerShowHidden(field huh.Field) bool { v := reflect.ValueOf(field) - if v.Kind() == reflect.Ptr { + if v.Kind() == reflect.Pointer { v = v.Elem() } picker := v.FieldByName("picker") @@ -63,7 +63,7 @@ func syncFilePickerDescription(form *huh.Form) { // Returns "" if the field is not a FilePicker. func filePickerCurrentDir(field huh.Field) string { v := reflect.ValueOf(field) - if v.Kind() == reflect.Ptr { + if v.Kind() == reflect.Pointer { v = v.Elem() } picker := v.FieldByName("picker") @@ -81,7 +81,7 @@ func filePickerCurrentDir(field huh.Field) string { // reflection. Returns "" if the field is not a FilePicker. func filePickerDescription(field huh.Field) string { v := reflect.ValueOf(field) - if v.Kind() == reflect.Ptr { + if v.Kind() == reflect.Pointer { v = v.Elem() } d := v.FieldByName("description") @@ -95,7 +95,7 @@ func filePickerDescription(field huh.Field) string { // Returns "" if the field is not a FilePicker. func filePickerTitle(field huh.Field) string { v := reflect.ValueOf(field) - if v.Kind() == reflect.Ptr { + if v.Kind() == reflect.Pointer { v = v.Elem() } t := v.FieldByName("title") diff --git a/internal/data/defaults.go b/internal/data/defaults.go index 12d20234..cab26e7d 100644 --- a/internal/data/defaults.go +++ b/internal/data/defaults.go @@ -9,6 +9,14 @@ import ( "time" ) +// Special `default` struct-tag values: defaultTagNow resolves to time.Now() +// for time.Time fields; defaultTagToday resolves to today's date (DateLayout) +// for string fields. +const ( + defaultTagNow = "now" + defaultTagToday = "today" +) + // ApplyDefaults sets zero-valued fields on the struct pointed to by v // to the values specified in their `default` struct tags. Fields that // are already non-zero are left untouched. Nested structs without a @@ -22,7 +30,7 @@ import ( // - Nested structs: recursed into (no tag needed on the struct field itself) func ApplyDefaults(v any) { rv := reflect.ValueOf(v) - if rv.Kind() != reflect.Ptr || rv.Elem().Kind() != reflect.Struct { + if rv.Kind() != reflect.Pointer || rv.Elem().Kind() != reflect.Struct { return } @@ -70,7 +78,7 @@ func StructDefault[T any](fieldName string) string { if rt == nil { return "" } - if rt.Kind() == reflect.Ptr { + if rt.Kind() == reflect.Pointer { rt = rt.Elem() } if rt.Kind() != reflect.Struct { @@ -89,7 +97,7 @@ func setFieldDefault(field reflect.Value, tag string) { //exhaustive:ignore // only handle types used in model/config defaults switch field.Kind() { case reflect.String: - if tag == "today" { + if tag == defaultTagToday { field.SetString(time.Now().Format(DateLayout)) } else { field.SetString(tag) @@ -111,7 +119,7 @@ func setFieldDefault(field reflect.Value, tag string) { } case reflect.Struct: - if field.Type() == reflect.TypeFor[time.Time]() && tag == "now" { + if field.Type() == reflect.TypeFor[time.Time]() && tag == defaultTagNow { field.Set(reflect.ValueOf(time.Now())) } } diff --git a/internal/data/duration.go b/internal/data/duration.go index 8c6c83b1..6707860b 100644 --- a/internal/data/duration.go +++ b/internal/data/duration.go @@ -9,6 +9,13 @@ import ( "time" ) +// Compressed-duration labels: durationNow for sub-minute spans, durationToday +// for a zero-day span. +const ( + durationNow = "now" + durationToday = "today" +) + // DateDiffDays returns the number of calendar days from now to target, // using each time's local Y/M/D. Positive means target is in the future. func DateDiffDays(now, target time.Time) int { @@ -30,7 +37,7 @@ func ShortDur(d time.Duration) string { } switch { case d < time.Minute: - return "now" + return durationNow case d < time.Hour: return fmt.Sprintf("%dm", int(d.Minutes())) case d < 24*time.Hour: @@ -47,7 +54,7 @@ func ShortDur(d time.Duration) string { // DaysText returns a bare compressed duration like "5d" or "today". func DaysText(days int) string { if days == 0 { - return "today" + return durationToday } abs := days if abs < 0 { @@ -59,7 +66,7 @@ func DaysText(days int) string { // PastDur returns a compressed past-duration string. Sub-minute is "<1m". func PastDur(d time.Duration) string { s := ShortDur(d) - if s == "now" { + if s == durationNow { return "<1m" } return s diff --git a/internal/data/migrate.go b/internal/data/migrate.go index 6d490b26..72c35979 100644 --- a/internal/data/migrate.go +++ b/internal/data/migrate.go @@ -5,6 +5,7 @@ package data import ( "fmt" + "slices" "sort" "strings" @@ -100,8 +101,7 @@ func migrateIntToStringIDs(db *gorm.DB) (retErr error) { // Phase 5: Drop old tables. if err := db.Transaction(func(tx *gorm.DB) error { order := migrationOrder() - for i := len(order) - 1; i >= 0; i-- { - m := order[i] + for _, m := range slices.Backward(order) { if _, ok := idMaps[m.table]; !ok { continue } diff --git a/internal/data/seed_scaled.go b/internal/data/seed_scaled.go index 54992972..375642f4 100644 --- a/internal/data/seed_scaled.go +++ b/internal/data/seed_scaled.go @@ -12,6 +12,23 @@ import ( "github.com/micasa-dev/micasa/internal/fake" ) +// Sample data values shared by the simple and scaled seed generators. +const ( + mimePDF = "application/pdf" + mimeJPEG = "image/jpeg" + + docTypeInvoice = "Invoice" + docTypeReceipt = "Receipt" + docTypeContract = "Contract" + + catAppliance = "Appliance" + catHVAC = "HVAC" + + demoOpCreate = "create" + sampleInvoicePDF = "invoice.pdf" + sampleReceiptPDF = "receipt.pdf" +) + // SeedSummary holds counts of generated entities for display after seeding. type SeedSummary struct { Vendors int @@ -198,7 +215,7 @@ func (s *Store) SeedScaledDataFrom(h *fake.HomeFaker, years int) (SeedSummary, e LastServicedAt: fm.LastServicedAt, CostCents: fm.CostCents, } - if catName == "Appliance" || catName == "HVAC" { + if catName == catAppliance || catName == catHVAC { ai := h.IntN(len(appliances)) item.ApplianceID = &appliances[ai].ID } @@ -357,7 +374,7 @@ func (s *Store) SeedScaledDataFrom(h *fake.HomeFaker, years int) (SeedSummary, e LastServicedAt: fm.LastServicedAt, CostCents: fm.CostCents, } - if (catName == "Appliance" || catName == "HVAC") && len(appliances) > 0 { + if (catName == catAppliance || catName == catHVAC) && len(appliances) > 0 { ai := h.IntN(len(appliances)) item.ApplianceID = &appliances[ai].ID } @@ -544,16 +561,16 @@ func seedBaseDocuments( seeds = append( seeds, docSeed{ - "Invoice", - "invoice.pdf", - "application/pdf", + docTypeInvoice, + sampleInvoicePDF, + mimePDF, DocumentEntityProject, projects[0].ID, }, docSeed{ - "Contract", + docTypeContract, "contract.pdf", - "application/pdf", + mimePDF, DocumentEntityProject, projects[1].ID, }, @@ -565,14 +582,14 @@ func seedBaseDocuments( docSeed{ "Warranty Card", "warranty-card.jpg", - "image/jpeg", + mimeJPEG, DocumentEntityAppliance, appliances[0].ID, }, docSeed{ "User Manual", "user-manual.pdf", - "application/pdf", + mimePDF, DocumentEntityAppliance, appliances[1].ID, }, @@ -584,7 +601,7 @@ func seedBaseDocuments( docSeed{ "Incident Photo", "incident-photo.jpg", - "image/jpeg", + mimeJPEG, DocumentEntityIncident, incidents[0].ID, }, @@ -614,16 +631,16 @@ var documentTemplates = []struct { fileName string mime string }{ - {"Invoice", "invoice.pdf", "application/pdf"}, - {"Receipt", "receipt.pdf", "application/pdf"}, - {"Contract", "contract.pdf", "application/pdf"}, - {"Warranty Card", "warranty-card.pdf", "application/pdf"}, - {"User Manual", "manual.pdf", "application/pdf"}, - {"Inspection Report", "inspection.pdf", "application/pdf"}, - {"Photo", "photo.jpg", "image/jpeg"}, - {"Estimate", "estimate.pdf", "application/pdf"}, - {"Permit", "permit.pdf", "application/pdf"}, - {"Insurance Claim", "claim.pdf", "application/pdf"}, + {docTypeInvoice, sampleInvoicePDF, mimePDF}, + {docTypeReceipt, sampleReceiptPDF, mimePDF}, + {docTypeContract, "contract.pdf", mimePDF}, + {"Warranty Card", "warranty-card.pdf", mimePDF}, + {"User Manual", "manual.pdf", mimePDF}, + {"Inspection Report", "inspection.pdf", mimePDF}, + {"Photo", "photo.jpg", mimeJPEG}, + {"Estimate", "estimate.pdf", mimePDF}, + {"Permit", "permit.pdf", mimePDF}, + {"Insurance Claim", "claim.pdf", mimePDF}, } // randomDocument creates a document linked to a random entity. @@ -695,53 +712,53 @@ func demoExtractionOps(docType string) []byte { switch docType { case "invoice": ops = []demoOp{ - {Action: "create", Table: TableVendors, Data: map[string]any{ - "name": "Garcia Plumbing", "email": "info@garcia.com", "phone": "555-0142", + {Action: demoOpCreate, Table: TableVendors, Data: map[string]any{ + ColName: "Garcia Plumbing", "email": "info@garcia.com", "phone": "555-0142", }}, - {Action: "create", Table: TableQuotes, Data: map[string]any{ - "total_cents": 28500, "notes": "Kitchen faucet replacement", + {Action: demoOpCreate, Table: TableQuotes, Data: map[string]any{ + "total_cents": 28500, ColNotes: "Kitchen faucet replacement", }}, {Action: "update", Table: TableDocuments, Data: map[string]any{ - "title": "Invoice #1047", + ColTitle: "Invoice #1047", }}, } case "contract": ops = []demoOp{ - {Action: "create", Table: TableVendors, Data: map[string]any{ - "name": "Summit Roofing Co", "email": "bids@summitroofing.com", + {Action: demoOpCreate, Table: TableVendors, Data: map[string]any{ + ColName: "Summit Roofing Co", "email": "bids@summitroofing.com", }}, - {Action: "create", Table: TableQuotes, Data: map[string]any{ + {Action: demoOpCreate, Table: TableQuotes, Data: map[string]any{ "total_cents": 875000, "labor_cents": 450000, "materials_cents": 425000, }}, {Action: "update", Table: TableDocuments, Data: map[string]any{ - "title": "Roofing Contract 2026", + ColTitle: "Roofing Contract 2026", }}, } case "estimate": ops = []demoOp{ - {Action: "create", Table: TableVendors, Data: map[string]any{ - "name": "Bright Electric", "phone": "555-0198", + {Action: demoOpCreate, Table: TableVendors, Data: map[string]any{ + ColName: "Bright Electric", "phone": "555-0198", }}, - {Action: "create", Table: TableQuotes, Data: map[string]any{ - "total_cents": 165000, "notes": "Panel upgrade to 200A", + {Action: demoOpCreate, Table: TableQuotes, Data: map[string]any{ + "total_cents": 165000, ColNotes: "Panel upgrade to 200A", }}, } case "receipt": ops = []demoOp{ - {Action: "create", Table: TableVendors, Data: map[string]any{ - "name": "Home Depot", "phone": "555-0100", + {Action: demoOpCreate, Table: TableVendors, Data: map[string]any{ + ColName: "Home Depot", "phone": "555-0100", }}, {Action: "update", Table: TableDocuments, Data: map[string]any{ - "title": "Receipt - Lumber & Hardware", + ColTitle: "Receipt - Lumber & Hardware", }}, } case "inspection": ops = []demoOp{ - {Action: "create", Table: TableMaintenanceItems, Data: map[string]any{ - "name": "HVAC inspection", "notes": "Annual inspection passed", + {Action: demoOpCreate, Table: TableMaintenanceItems, Data: map[string]any{ + ColName: "HVAC inspection", ColNotes: "Annual inspection passed", }}, {Action: "update", Table: TableDocuments, Data: map[string]any{ - "title": "HVAC Inspection Report", + ColTitle: "HVAC Inspection Report", }}, } default: @@ -758,11 +775,11 @@ func demoExtractionOps(docType string) []byte { // or nil if the document type doesn't typically produce extraction results. func demoOpsForTemplate(title string) []byte { switch title { - case "Invoice": + case docTypeInvoice: return demoExtractionOps("invoice") - case "Receipt": + case docTypeReceipt: return demoExtractionOps("receipt") - case "Contract": + case docTypeContract: return demoExtractionOps("contract") case "Estimate": return demoExtractionOps("estimate") diff --git a/internal/data/sqlite/migrator.go b/internal/data/sqlite/migrator.go index c7e7ce15..dae9a2fb 100644 --- a/internal/data/sqlite/migrator.go +++ b/internal/data/sqlite/migrator.go @@ -12,6 +12,7 @@ import ( "database/sql" "errors" "fmt" + "slices" "strings" "gorm.io/gorm" @@ -53,8 +54,8 @@ func (m Migrator) DropTable(values ...any) error { values = m.ReorderModels(values, false) tx := m.DB.Session(&gorm.Session{}) - for i := len(values) - 1; i >= 0; i-- { - if err := m.RunWithValue(values[i], func(stmt *gorm.Statement) error { + for _, v := range slices.Backward(values) { + if err := m.RunWithValue(v, func(stmt *gorm.Statement) error { return tx.Exec( "DROP TABLE IF EXISTS ?", clause.Table{Name: stmt.Table}, diff --git a/internal/data/sqlite/sqlite.go b/internal/data/sqlite/sqlite.go index 034cafd0..636663e4 100644 --- a/internal/data/sqlite/sqlite.go +++ b/internal/data/sqlite/sqlite.go @@ -32,6 +32,12 @@ import ( // DriverName is the default driver name for SQLite. const DriverName = "sqlite" +// SQLite column affinities used by DataTypeOf. +const ( + colTypeInteger = "integer" + colTypeText = "text" +) + type Dialector struct { DriverName string DSN string @@ -287,13 +293,13 @@ func (dialector Dialector) DataTypeOf(field *schema.Field) string { return "numeric" case schema.Int, schema.Uint: if field.AutoIncrement { - return "integer PRIMARY KEY AUTOINCREMENT" + return colTypeInteger + " PRIMARY KEY AUTOINCREMENT" } - return "integer" + return colTypeInteger case schema.Float: return "real" case schema.String: - return "text" + return colTypeText case schema.Time: if val, ok := field.TagSettings["TYPE"]; ok { return val diff --git a/internal/data/store_seed.go b/internal/data/store_seed.go index 089ac7c7..387fb82b 100644 --- a/internal/data/store_seed.go +++ b/internal/data/store_seed.go @@ -181,7 +181,7 @@ func (s *Store) SeedDemoDataFrom(h *fake.HomeFaker) error { CostCents: fm.CostCents, } // Link appliance-related items to a random appliance. - if catName == "Appliance" || catName == "HVAC" { + if catName == catAppliance || catName == catHVAC { ai := h.IntN(len(appliances)) item.ApplianceID = &appliances[ai].ID } @@ -249,19 +249,19 @@ func (s *Store) SeedDemoDataFrom(h *fake.HomeFaker) error { } docSeeds := []docSeed{ { - "Invoice", "invoice.pdf", "application/pdf", + docTypeInvoice, sampleInvoicePDF, mimePDF, DocumentEntityProject, projects[0].ID, demoExtractionOps("invoice"), }, { - "Contract", "contract.pdf", "application/pdf", + docTypeContract, "contract.pdf", mimePDF, DocumentEntityProject, projects[1].ID, demoExtractionOps("contract"), }, { "Warranty Card", "warranty-card.jpg", - "image/jpeg", + mimeJPEG, DocumentEntityAppliance, appliances[0].ID, nil, @@ -269,7 +269,7 @@ func (s *Store) SeedDemoDataFrom(h *fake.HomeFaker) error { { "User Manual", "user-manual.pdf", - "application/pdf", + mimePDF, DocumentEntityAppliance, appliances[1].ID, nil, @@ -277,7 +277,7 @@ func (s *Store) SeedDemoDataFrom(h *fake.HomeFaker) error { { "Incident Photo", "incident-photo.jpg", - "image/jpeg", + mimeJPEG, DocumentEntityIncident, incidents[0].ID, nil, @@ -307,11 +307,11 @@ func (s *Store) SeedDemoDataFrom(h *fake.HomeFaker) error { func (s *Store) seedProjectTypes() error { types := []ProjectType{ - {Name: "Appliance"}, + {Name: catAppliance}, {Name: "Electrical"}, {Name: "Exterior"}, {Name: "Flooring"}, - {Name: "HVAC"}, + {Name: catHVAC}, {Name: "Landscaping"}, {Name: "Painting"}, {Name: "Plumbing"}, @@ -331,10 +331,10 @@ func (s *Store) seedProjectTypes() error { func (s *Store) seedMaintenanceCategories() error { categories := []MaintenanceCategory{ - {Name: "Appliance"}, + {Name: catAppliance}, {Name: "Electrical"}, {Name: "Exterior"}, - {Name: "HVAC"}, + {Name: catHVAC}, {Name: "Interior"}, {Name: "Landscaping"}, {Name: "Plumbing"}, diff --git a/internal/llm/prompt.go b/internal/llm/prompt.go index 877b99d0..3ec2499b 100644 --- a/internal/llm/prompt.go +++ b/internal/llm/prompt.go @@ -5,6 +5,7 @@ package llm import ( "fmt" + "slices" "strings" "time" @@ -250,7 +251,7 @@ func ExtractSQL(raw string) string { lines = lines[1:] } // Remove closing fence - for i := len(lines) - 1; i >= 0; i-- { + for i := range slices.Backward(lines) { if strings.TrimSpace(lines[i]) == "```" { lines = lines[:i] break diff --git a/internal/locale/currency.go b/internal/locale/currency.go index 33658f0d..b78e7191 100644 --- a/internal/locale/currency.go +++ b/internal/locale/currency.go @@ -34,6 +34,10 @@ type Currency struct { const nbsp = "\u00a0" // non-breaking space between number and suffix symbol +// defaultCurrencyCode is the ISO 4217 fallback when no code is configured +// or detectable. +const defaultCurrencyCode = "USD" + var ( ErrInvalidMoney = errors.New("invalid money value") ErrNegativeMoney = errors.New("negative money value") @@ -44,7 +48,7 @@ var ( // number grouping, decimal separator, and symbol placement. func Resolve(code string, tag language.Tag) (Currency, error) { if code == "" { - code = "USD" + code = defaultCurrencyCode } code = strings.ToUpper(strings.TrimSpace(code)) unit, err := currency.ParseISO(code) @@ -75,7 +79,7 @@ func MustResolve(code string, tag language.Tag) Currency { // DefaultCurrency returns USD with standard US English formatting. func DefaultCurrency() Currency { - return MustResolve("USD", language.AmericanEnglish) + return MustResolve(defaultCurrencyCode, language.AmericanEnglish) } // ResolveDefault resolves the currency code using the config layering: @@ -90,7 +94,7 @@ func ResolveDefault(configured string) (Currency, error) { code = detectCurrencyFromLocale() } if code == "" { - code = "USD" + code = defaultCurrencyCode } return Resolve(code, DetectLocale()) } diff --git a/internal/sqlfmt/sqlfmt.go b/internal/sqlfmt/sqlfmt.go index 1988379b..92a7dc2a 100644 --- a/internal/sqlfmt/sqlfmt.go +++ b/internal/sqlfmt/sqlfmt.go @@ -180,7 +180,7 @@ func tokenizeSQL(s string) []sqlToken { // -1 = not a clause keyword. func clauseLevel(kw string) int { switch kw { - case "SELECT", "FROM", "WHERE", + case kwSelect, "FROM", "WHERE", "ORDER BY", "GROUP BY", "HAVING", "LIMIT", "OFFSET", "UNION", "UNION ALL", "INTERSECT", "EXCEPT", @@ -207,9 +207,12 @@ var multiWordClauses = []string{ "FULL JOIN", } +// kwSelect is the SELECT keyword; referenced often enough to warrant a name. +const kwSelect = "SELECT" + // sqlKeywords is the set of SQL reserved words that get uppercased. var sqlKeywords = map[string]bool{ - "SELECT": true, "DISTINCT": true, "FROM": true, "WHERE": true, + kwSelect: true, "DISTINCT": true, "FROM": true, "WHERE": true, "AND": true, "OR": true, "NOT": true, "IN": true, "EXISTS": true, "BETWEEN": true, "LIKE": true, "IS": true, "NULL": true, "AS": true, "ON": true, "JOIN": true, @@ -374,7 +377,7 @@ func layoutClauses(rawTokens []sqlToken) string { for nextIdx < len(tokens) && tokens[nextIdx].Kind == tokSpace { nextIdx++ } - if nextIdx < len(tokens) && tokens[nextIdx].Keyword == "SELECT" { + if nextIdx < len(tokens) && tokens[nextIdx].Keyword == kwSelect { baseIndent++ } atLineStart = false @@ -400,7 +403,7 @@ func layoutClauses(rawTokens []sqlToken) string { kw := ct.Keyword // SELECT: new line with proper indentation - if kw == "SELECT" { + if kw == kwSelect { if b.Len() > 0 { trimTrailingSpace(&b) b.WriteString("\n") diff --git a/nix/overlay.nix b/nix/overlay.nix index 1c29e439..4ec85819 100644 --- a/nix/overlay.nix +++ b/nix/overlay.nix @@ -8,23 +8,25 @@ _final: prev: let - # Scoped Go 1.26.2 override for micasa and its dev tools only. + # Scoped Go 1.26.3 override for micasa and its dev tools only. # NOT exported as go/go_1_26/buildGoModule — doing so rebuilds the # entire transitive closure from source (VHS → Chromium → PipeWire → # ffmpeg/gstreamer) because every Go derivation's input hash changes. # - # 1.26.2 fixes five stdlib vulnerabilities flagged by govulncheck: - # GO-2026-4865 (html/template JsBraceDepth XSS) - # GO-2026-4866 (crypto/x509 excludedSubtrees auth bypass) - # GO-2026-4870 (crypto/tls KeyUpdate DoS) - # GO-2026-4946 (crypto/x509 inefficient policy validation) - # GO-2026-4947 (crypto/x509 unexpected work during chain building) - # Drop this override once nixpkgs picks up Go 1.26.2. + # 1.26.3 fixes six stdlib vulnerabilities flagged by govulncheck: + # GO-2026-4918 (net/http/internal/http2 infinite loop on bad SETTINGS) + # GO-2026-4971 (net Dial/LookupPort panic on NUL byte on Windows) + # GO-2026-4977 (net/mail quadratic concatenation in consumePhrase) + # GO-2026-4980 (html/template escaper bypass XSS) + # GO-2026-4982 (html/template meta content URL escaping bypass XSS) + # GO-2026-4986 (net/mail quadratic concatenation in consumeComment) + # (1.26.2's html/template, crypto/x509, and crypto/tls fixes are + # carried forward.) Drop this override once nixpkgs picks up Go 1.26.3. patchedGo = prev.go_1_26.overrideAttrs (_: rec { - version = "1.26.2"; + version = "1.26.3"; src = prev.fetchurl { url = "https://go.dev/dl/go${version}.src.tar.gz"; - hash = "sha256-LpHrtpR6lulDb7KzkmqIAu/mOm03Xf/sT4Kqnb1v1Ds="; + hash = "sha256-HGRoddCqh5kTMYTtV895/yS97+jIggRwYCqdPW2Rkrg="; }; }); in diff --git a/nix/package.nix b/nix/package.nix index 7ac2cd33..ec0826e6 100644 --- a/nix/package.nix +++ b/nix/package.nix @@ -14,7 +14,7 @@ buildGoModule { inherit pname version; src = gitignoreSource ../.; subPackages = [ "cmd/micasa" ]; - vendorHash = "sha256-m8rOq+BSarWL7gNNuSmhH5Qq8LqSs2ZCBT0F0DVJYtA="; + vendorHash = "sha256-lj+ZkNzTSFue5PDc4oOFxtGkqbEhwBzO9OaK+KGU0I8="; env.CGO_ENABLED = 0; preCheck = '' export HOME="$(mktemp -d)"