Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c72371ce90 | ||
|
|
b7559a9451 | ||
|
|
a63bdb8c37 | ||
|
|
4948afc393 |
@@ -0,0 +1,26 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"log"
|
||||
"net/http"
|
||||
|
||||
"cmbuyer/admin/internal/server"
|
||||
)
|
||||
|
||||
const listenAddress = ":8080"
|
||||
|
||||
func main() {
|
||||
if err := run(); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func run() error {
|
||||
err := http.ListenAndServe(listenAddress, server.NewRouter())
|
||||
if errors.Is(err, http.ErrServerClosed) {
|
||||
return nil
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
module cmbuyer/admin
|
||||
|
||||
go 1.23.0
|
||||
|
||||
require (
|
||||
github.com/gin-gonic/gin v1.11.0
|
||||
github.com/mattn/go-sqlite3 v1.14.49
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/bytedance/sonic v1.14.0 // indirect
|
||||
github.com/bytedance/sonic/loader v0.3.0 // indirect
|
||||
github.com/cloudwego/base64x v0.1.6 // indirect
|
||||
github.com/gabriel-vasile/mimetype v1.4.8 // indirect
|
||||
github.com/gin-contrib/sse v1.1.0 // indirect
|
||||
github.com/go-playground/locales v0.14.1 // indirect
|
||||
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||
github.com/go-playground/validator/v10 v10.27.0 // indirect
|
||||
github.com/goccy/go-json v0.10.2 // indirect
|
||||
github.com/goccy/go-yaml v1.18.0 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
|
||||
github.com/leodido/go-urn v1.4.0 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
|
||||
github.com/quic-go/qpack v0.5.1 // indirect
|
||||
github.com/quic-go/quic-go v0.54.0 // indirect
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||
github.com/ugorji/go/codec v1.3.0 // indirect
|
||||
go.uber.org/mock v0.5.0 // indirect
|
||||
golang.org/x/arch v0.20.0 // indirect
|
||||
golang.org/x/crypto v0.40.0 // indirect
|
||||
golang.org/x/mod v0.25.0 // indirect
|
||||
golang.org/x/net v0.42.0 // indirect
|
||||
golang.org/x/sync v0.16.0 // indirect
|
||||
golang.org/x/sys v0.35.0 // indirect
|
||||
golang.org/x/text v0.27.0 // indirect
|
||||
golang.org/x/tools v0.34.0 // indirect
|
||||
google.golang.org/protobuf v1.36.9 // indirect
|
||||
)
|
||||
@@ -0,0 +1,90 @@
|
||||
github.com/bytedance/sonic v1.14.0 h1:/OfKt8HFw0kh2rj8N0F6C/qPGRESq0BbaNZgcNXXzQQ=
|
||||
github.com/bytedance/sonic v1.14.0/go.mod h1:WoEbx8WTcFJfzCe0hbmyTGrfjt8PzNEBdxlNUO24NhA=
|
||||
github.com/bytedance/sonic/loader v0.3.0 h1:dskwH8edlzNMctoruo8FPTJDF3vLtDT0sXZwvZJyqeA=
|
||||
github.com/bytedance/sonic/loader v0.3.0/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI=
|
||||
github.com/cloudwego/base64x v0.1.6 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI2M=
|
||||
github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gEHfghB2IPU=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM=
|
||||
github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8=
|
||||
github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w=
|
||||
github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM=
|
||||
github.com/gin-gonic/gin v1.11.0 h1:OW/6PLjyusp2PPXtyxKHU0RbX6I/l28FTdDlae5ueWk=
|
||||
github.com/gin-gonic/gin v1.11.0/go.mod h1:+iq/FyxlGzII0KHiBGjuNn4UNENUlKbGlNmc+W50Dls=
|
||||
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
|
||||
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
||||
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
|
||||
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
|
||||
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
|
||||
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
||||
github.com/go-playground/validator/v10 v10.27.0 h1:w8+XrWVMhGkxOaaowyKH35gFydVHOvC0/uWoy2Fzwn4=
|
||||
github.com/go-playground/validator/v10 v10.27.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo=
|
||||
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
|
||||
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||
github.com/goccy/go-yaml v1.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw=
|
||||
github.com/goccy/go-yaml v1.18.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
|
||||
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/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||
github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
|
||||
github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
|
||||
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
|
||||
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-sqlite3 v1.14.49 h1:B8jBHC3xhxZgxztrgruTuLucebnULQnx4W7cF7SAE9w=
|
||||
github.com/mattn/go-sqlite3 v1.14.49/go.mod h1:6JTjA44L93a0QCyJef5YvlPoKXntQPjzWv5gtm9sB6w=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
|
||||
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI=
|
||||
github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg=
|
||||
github.com/quic-go/quic-go v0.54.0 h1:6s1YB9QotYI6Ospeiguknbp2Znb/jZYjZLRXn9kMQBg=
|
||||
github.com/quic-go/quic-go v0.54.0/go.mod h1:e68ZEaCdyviluZmy44P6Iey98v/Wfz6HCjQEm+l8zTY=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
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/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
|
||||
github.com/ugorji/go/codec v1.3.0 h1:Qd2W2sQawAfG8XSvzwhBeoGq71zXOC/Q1E9y/wUcsUA=
|
||||
github.com/ugorji/go/codec v1.3.0/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4=
|
||||
go.uber.org/mock v0.5.0 h1:KAMbZvZPyBPWgD14IrIQ38QCyjwpvVVV6K/bHl1IwQU=
|
||||
go.uber.org/mock v0.5.0/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM=
|
||||
golang.org/x/arch v0.20.0 h1:dx1zTU0MAE98U+TQ8BLl7XsJbgze2WnNKF/8tGp/Q6c=
|
||||
golang.org/x/arch v0.20.0/go.mod h1:bdwinDaKcfZUGpH09BB7ZmOfhalA8lQdzl62l8gGWsk=
|
||||
golang.org/x/crypto v0.40.0 h1:r4x+VvoG5Fm+eJcxMaY8CQM7Lb0l1lsmjGBQ6s8BfKM=
|
||||
golang.org/x/crypto v0.40.0/go.mod h1:Qr1vMER5WyS2dfPHAlsOj01wgLbsyWtFn/aY+5+ZdxY=
|
||||
golang.org/x/mod v0.25.0 h1:n7a+ZbQKQA/Ysbyb0/6IbB1H/X41mKgbhfv7AfG/44w=
|
||||
golang.org/x/mod v0.25.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww=
|
||||
golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs=
|
||||
golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8=
|
||||
golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw=
|
||||
golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI=
|
||||
golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4=
|
||||
golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU=
|
||||
golang.org/x/tools v0.34.0 h1:qIpSLOxeCYGg9TrcJokLBG4KFA6d795g0xkBkiESGlo=
|
||||
golang.org/x/tools v0.34.0/go.mod h1:pAP9OwEaY1CAW3HOmg3hLZC5Z0CCmzjAF2UQMSqNARg=
|
||||
google.golang.org/protobuf v1.36.9 h1:w2gp2mA27hUeUzj9Ex9FBjsBm40zfaDtEWow293U7Iw=
|
||||
google.golang.org/protobuf v1.36.9/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
@@ -0,0 +1,19 @@
|
||||
// Package server 定义采购服务当前拥有的 HTTP 端点。
|
||||
package server
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// NewRouter 返回当前服务范围内的完整 HTTP 路由。
|
||||
func NewRouter() *gin.Engine {
|
||||
router := gin.New()
|
||||
|
||||
router.GET("/healthz", func(context *gin.Context) {
|
||||
context.JSON(http.StatusOK, gin.H{"status": "ok"})
|
||||
})
|
||||
|
||||
return router
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package server_test
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"cmbuyer/admin/internal/server"
|
||||
)
|
||||
|
||||
func TestHealthz(t *testing.T) {
|
||||
request := httptest.NewRequest(http.MethodGet, "/healthz", nil)
|
||||
response := httptest.NewRecorder()
|
||||
|
||||
server.NewRouter().ServeHTTP(response, request)
|
||||
|
||||
if response.Code != http.StatusOK {
|
||||
t.Fatalf("healthz status = %d, want %d", response.Code, http.StatusOK)
|
||||
}
|
||||
|
||||
if contentType := response.Header().Get("Content-Type"); contentType != "application/json; charset=utf-8" {
|
||||
t.Fatalf("healthz content type = %q, want application/json; charset=utf-8", contentType)
|
||||
}
|
||||
|
||||
if body := response.Body.String(); body != "{\"status\":\"ok\"}" {
|
||||
t.Fatalf("healthz body = %q, want {\"status\":\"ok\"}", body)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
// Package sqlite 提供采购服务的 SQLite 驱动注册。
|
||||
package sqlite
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
|
||||
_ "github.com/mattn/go-sqlite3"
|
||||
)
|
||||
|
||||
// Open 打开 SQLite 数据源;调用方负责其 schema 与生命周期。
|
||||
func Open(dataSourceName string) (*sql.DB, error) {
|
||||
return sql.Open("sqlite3", dataSourceName)
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package sqlite_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"cmbuyer/admin/internal/storage/sqlite"
|
||||
)
|
||||
|
||||
func TestOpen(t *testing.T) {
|
||||
database, err := sqlite.Open(":memory:")
|
||||
if err != nil {
|
||||
t.Fatalf("open SQLite database: %v", err)
|
||||
}
|
||||
t.Cleanup(func() {
|
||||
if err := database.Close(); err != nil {
|
||||
t.Errorf("close SQLite database: %v", err)
|
||||
}
|
||||
})
|
||||
|
||||
if err := database.PingContext(context.Background()); err != nil {
|
||||
t.Fatalf("ping SQLite database: %v", err)
|
||||
}
|
||||
}
|
||||
@@ -85,13 +85,14 @@ cmbuyer 是一个自动化采购系统:**采购服务**(网页端,`admin/`
|
||||
|
||||
## 当前阶段
|
||||
|
||||
**Phase 0 · 地基(尚未开始编码)。** 仓库目前只有文档,没有生产代码。
|
||||
**Phase 0 · 地基。** 采购服务骨架已初始化,采购工具骨架仍在进行;尚无采购业务代码。
|
||||
|
||||
优先路径:
|
||||
执行按任务依赖驱动,**不按 Phase 整段串行等待**。当前优先路径:
|
||||
|
||||
1. Phase 0:网页端 / 桌面端 MVP 原型人工确认、两端骨架与数据模型。
|
||||
2. **Phase 1:真机取证(最高风险,生死线)。**
|
||||
3. Phase 2:采购服务核心(含**授权超时**,不得推后)。
|
||||
1. 继续完成 T-002 采购工具骨架;采购服务方向可并行推进 T-004 数据模型。
|
||||
2. T-002 完成后立即推进 **T-101 → T-102 → T-103 真机取证**,并可并行补 T-003 统一入口。
|
||||
3. T-103 结论确认后,才开始依赖真机可读字段的 Phase 2 生产页面;采购服务核心与
|
||||
T-104 → T-107 后续真机安全判据按依赖并行推进。
|
||||
4. Phase 3:双端打通与**第一趟试选**端到端。
|
||||
5. Phase 4:**第二趟下单**与收尾。
|
||||
6. V2 及以后:图搜、Excel、ERP、订单自动核对、AI 辅助。
|
||||
@@ -171,15 +172,17 @@ cmbuyer 是一个自动化采购系统:**采购服务**(网页端,`admin/`
|
||||
|
||||
## 验证命令
|
||||
|
||||
> **占位符。** T-001 / T-002 落地后由该任务替换为真实命令,并同步
|
||||
> [`03-tech-stack.md`](03-tech-stack.md) 和 [`current-state.md`](current-state.md)。
|
||||
> 采购服务命令已由 T-001 实际验证;采购工具命令在 T-002 完成前仍是占位路径。
|
||||
|
||||
```bash
|
||||
# 采购服务 admin/(改了 Go 代码后)
|
||||
cd admin
|
||||
go test ./...
|
||||
go vet ./...
|
||||
go build ./...
|
||||
|
||||
# 采购工具 client/(改了 Python 代码后)
|
||||
# 采购工具 client/(T-002 完成后启用)
|
||||
cd client
|
||||
python -m unittest discover -s tests -t .
|
||||
python -m compileall -q src tests
|
||||
|
||||
|
||||
@@ -59,14 +59,13 @@
|
||||
|
||||
## 五、构建与运行命令
|
||||
|
||||
> **占位符。** 代码尚未初始化,以下命令分别在 T-001 / T-002 落地后由对应任务替换为真实可运行命令,
|
||||
> 并同步到 [`00-ai-start-here.md`](00-ai-start-here.md) 和 [`current-state.md`](current-state.md)。
|
||||
> 采购服务命令已由 T-001 实际验证;采购工具命令由 T-002 完成后替换剩余占位项。
|
||||
|
||||
| 用途 | 采购服务(`admin/`) | 采购工具(`client/`) |
|
||||
| --- | --- | --- |
|
||||
| 安装依赖 | `go mod download` | `python -m venv .venv` + `pip install -r requirements.txt` |
|
||||
| 本地开发 | 【T-001 填写】 | 【T-002 填写】 |
|
||||
| 构建 | 【T-001 填写】 | 【T-002 填写】 |
|
||||
| 本地开发 | `go run ./cmd/server` | 【T-002 填写】 |
|
||||
| 构建 | `go build ./...` | 【T-002 填写】 |
|
||||
| 测试 | `go test ./...` | `python -m unittest discover -s tests -t .` |
|
||||
| 静态检查 | `go vet ./...` | `python -m compileall -q src tests` |
|
||||
|
||||
|
||||
@@ -15,6 +15,27 @@
|
||||
4. **本文只在规划变化时修改**:单个任务开工或完成**不**修改本文。
|
||||
5. **动手前**先读 `00-ai-start-here.md`、`05-coding-rules.md` 和 `current-state.md`。
|
||||
|
||||
## 并行执行与关键路径
|
||||
|
||||
执行顺序以任务表中的 `依赖` 为准,Phase 是里程碑分组,**不是要求整段串行的栅栏**。
|
||||
在 `write_paths` 不重叠且依赖已完成的前提下,按以下波次推进:
|
||||
|
||||
| 波次 | 采购服务方向 | 采购工具 / 真机方向 | 集成方向 |
|
||||
| --- | --- | --- | --- |
|
||||
| 1 | T-001 初始化 `admin/` | T-002 初始化 `client/` | - |
|
||||
| 2 | T-001 后立即做 T-004 | T-002 后立即做 T-101 → T-102 → **T-103** | T-001、T-002 都完成后做 T-003 |
|
||||
| 3 | T-103 通过后推进 T-201 → T-207 | T-103 后推进 T-104 → T-107 | T-208 等待 T-207 与 T-107 均完成 |
|
||||
| 4 | T-301 → T-302 | T-302 后交接 T-303 → T-304 → T-306 | T-306 与 T-104 完成后做 T-305 |
|
||||
| 5 | - | T-305、T-208、T-306 后做 T-401 | T-401 后并行 T-402 / T-403,再做 T-404 → T-405 |
|
||||
|
||||
**T-103 是当前最高优先级和 MVP 生死线。** T-002 一完成就启动 T-101,不等待 T-003、
|
||||
T-004 或整个 Phase 0 收尾。并行只优化等待关系,不改变下列门禁:
|
||||
|
||||
- T-103 的真机结论出来前,不写依赖真机可读字段的 Phase 2 生产页面。
|
||||
- `needs_device: true` 的任务仍只能由人完成验收。
|
||||
- 不复用前序项目页面判据,不放宽三道价格闸门,不让第一趟引用任何下单函数。
|
||||
- 第三个 agent 优先做写路径独立的集成任务或只读复核,不与两端任务争写共享文档。
|
||||
|
||||
## 建议拆分清单
|
||||
|
||||
### Phase 0 · 地基
|
||||
|
||||
+36
-32
@@ -12,30 +12,29 @@
|
||||
## 当前快照
|
||||
|
||||
- 日期:2026-08-03
|
||||
- 阶段:**Phase 0 · 地基(先确认原型,尚未开始生产编码)**
|
||||
- 阶段:**Phase 0 · 地基(采购服务骨架完成,采购工具骨架进行中)**
|
||||
- MVP 形态:手工填链接建单 → 批量开始试选 → 定时轮询 → **第一趟试选** → 人工确认 → **第二趟下单** → 待付款
|
||||
- 技术栈:已定。采购服务(`admin/`)使用 Go 1.23+ / gin / SQLite;采购工具(`client/`)
|
||||
使用 Python 3.11+ / uiautomator2 / PySide6。
|
||||
详见 [`03-tech-stack.md`](03-tech-stack.md)
|
||||
- 生产代码:**无**。仓库目前只有文档
|
||||
- 测试:**无**
|
||||
- 生产代码:`admin/` 已有最小 Go 服务、`GET /healthz` 与 SQLite 驱动封装;尚无采购业务代码
|
||||
- 测试:采购服务 2 项离线单元测试
|
||||
- 数据:**无**
|
||||
- 标准启动路径:`./init.ps1`(Windows)/ `./init.sh`。已存在,以 `admin/go.mod` 和
|
||||
`client/requirements.txt` 判断两端是否初始化;当前会以退出码 3 结束并列出 T-001 / T-002
|
||||
——这是预期行为
|
||||
- 标准验证路径:目前只有 `python scripts/validate_agent_context.py` 可跑通
|
||||
- 当前 blocker:T-005“采购服务”Web 表格工作台和 T-006 第三轮“采购工具”桌面工作区均已通过
|
||||
自动检查;两者经人工确认前不开始对应生产 UI。
|
||||
- 标准启动路径:`./init.ps1`(Windows)/ `./init.sh`。当前会验证采购服务,再因
|
||||
`client/requirements.txt` 尚不存在以退出码 3 提示 T-002——这是预期行为
|
||||
- 标准验证路径:`admin/` 下运行 `go test ./...`、`go vet ./...`、`go build ./...`;仓库根运行
|
||||
`python scripts/validate_agent_context.py`
|
||||
- 当前 blocker:无外部 blocker。T-002 正在实现;T-004 已因 T-001 完成而可领取。
|
||||
|
||||
## 当前目录要点
|
||||
|
||||
| 路径 | 状态 | 说明 |
|
||||
| --- | --- | --- |
|
||||
| `docs/` | 已有 | 项目规范化文档,本次已完整生成 |
|
||||
| `docs/tasks/` | 已有(2 个活动任务) | T-005 / T-006 均为 `DOING`,含 `README.md` 与 `_template.md` |
|
||||
| `docs/design/` | 已有(6 个原型) | web 登录 / 建单 / 工作台 / 详情,desk 采购执行 / 配置;待人工确认 |
|
||||
| `scripts/` | 已有 | `validate_agent_context.py` |
|
||||
| `admin/` | **待建** | “采购服务”Go 后端与管理页面(T-001) |
|
||||
| `docs/tasks/` | 已有(T-001、T-002、T-005~T-009) | T-001 已完成;T-002 正在进行;其余既有任务已完成 |
|
||||
| `docs/design/` | 已有(6 个原型) | web 登录 / 建单 / 工作台 / 详情,desk 采购执行 / 配置;均已人工确认 |
|
||||
| `scripts/` | 已有 | 上下文门禁、Vikunja 单向导出与 MCP 启动包装 |
|
||||
| `admin/` | 已初始化 | Go 1.23+ / gin / SQLite 骨架与健康检查;无采购业务路由 |
|
||||
| `client/` | **待建** | “采购工具”Python 桌面端(T-002) |
|
||||
| `init.ps1` / `init.sh` | 已有(骨架) | 统一入口。两端目录建好后由 T-003 补全并验证 |
|
||||
|
||||
@@ -43,33 +42,38 @@
|
||||
|
||||
任务状态以 `docs/tasks/` 各任务文件 frontmatter 的 `status` 为准。本节只写项目级摘要:
|
||||
|
||||
- 已完成:T-007(项目 / 产品名称与 `admin/`、`client/` 源码目录契约)。
|
||||
- 正在进行:T-005(“采购服务”网页端 MVP 交互原型)、T-006(桌面端 MVP 交互原型)。
|
||||
T-005 已将三泳道改为传统表格并统一网页端品牌名称;T-006 第三轮已把执行记录详情从模态框
|
||||
改为左侧内联双视图,支持连续选行、可见返回按钮、`Esc` 层级、异常读取状态和紧凑宽度
|
||||
单视图。两项自动验证均已完成,都因 `needs_human_review: true` 保持 `DOING`。
|
||||
- **下一步:人工浏览 6 个原型,重点确认 Web 表格与详情抽屉,以及桌面端顶部状态区、图片
|
||||
证据、两列执行记录和左侧内联详情 / 返回路径。** 明确确认后才能把 T-005 / T-006 改为
|
||||
`DONE`。
|
||||
- 原型使用假数据、不调用真实接口、不驱动真机;T-001 / T-002 骨架可在确认后并行,
|
||||
Phase 2 生产页面还必须等待相关真机结论。
|
||||
- 已完成:T-005(采购服务交互原型)、T-006(采购工具交互原型)、T-007(产品名称与
|
||||
源码目录契约)、T-008(Vikunja 任务权威与单向导出)、T-009(MVP 关键路径与并行波次),
|
||||
以及 T-001(采购服务 Go 骨架)。
|
||||
- 正在进行:T-002(采购工具 Python 骨架)。采购服务方向可并行落成 T-004;T-002 完成后
|
||||
立即推进 T-101 → T-102 → T-103,并可补 T-003 统一入口。
|
||||
- T-103 是当前最高优先级和 MVP 生死线。通过前不开发依赖真机可读字段的 Phase 2 生产页面。
|
||||
- 已确认原型继续只作信息架构依据;原型假数据不调用真实接口、不驱动真机。真机结论改变
|
||||
可读字段时必须先回修原型与交互清单。
|
||||
|
||||
## 当前可运行内容
|
||||
|
||||
**目前没有可运行的代码。** 唯一可运行的检查:
|
||||
采购服务当前可运行:
|
||||
|
||||
```bash
|
||||
# 校验 agent 上下文清单
|
||||
cd admin
|
||||
go mod download
|
||||
go run ./cmd/server
|
||||
```
|
||||
|
||||
采购服务验证与仓库级上下文门禁:
|
||||
|
||||
```bash
|
||||
cd admin
|
||||
go test ./...
|
||||
go vet ./...
|
||||
go build ./...
|
||||
|
||||
cd ..
|
||||
python scripts/validate_agent_context.py
|
||||
```
|
||||
|
||||
T-001 / T-002 完成后,本节替换为真实命令:
|
||||
|
||||
```bash
|
||||
# 采购服务 admin/(T-001 后填写)
|
||||
# 采购工具 client/(T-002 后填写)
|
||||
# 统一入口(T-003 后填写)
|
||||
```
|
||||
采购工具与统一入口的真实命令分别等待 T-002、T-003 完成后补齐。
|
||||
|
||||
## 关键背景
|
||||
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
---
|
||||
id: T-001
|
||||
title: 初始化采购服务 admin Go 骨架
|
||||
phase: 0
|
||||
deps: []
|
||||
status: DONE
|
||||
created: 2026-08-03
|
||||
vikunja_task_id: 18
|
||||
context_ref: a63bdb8
|
||||
work_branch: task/t-001-admin-skeleton
|
||||
needs_device: false
|
||||
needs_human_review: false
|
||||
write_paths:
|
||||
- docs/tasks/T-001.md
|
||||
- admin/**
|
||||
- docs/03-tech-stack.md
|
||||
- docs/00-ai-start-here.md
|
||||
- docs/current-state.md
|
||||
---
|
||||
|
||||
<!-- BEGIN VIKUNJA EXPORT id=18 synced=2026-08-03T10:13:08Z sha256=81e7657246ae88d8b8c8c9eeb370989c11a524a7ed2e7bd25d647dc77efd747e -->
|
||||
## 问题 / 背景
|
||||
|
||||
仓库尚无 admin/go.mod 和可运行的采购服务代码,init.ps1 因此按预期以退出码 3 停止。没有稳定的 Go 骨架、健康检查和真实验证命令,后续数据模型与服务端任务无法开始。
|
||||
|
||||
## 关联需求与交互
|
||||
|
||||
- 功能:Phase 0 工程地基,不新增业务功能。
|
||||
- 用户故事 / 交互:不适用,不实现生产页面。
|
||||
- 架构 / API:遵循 docs/03-tech-stack.md;只提供健康检查,不新增采购业务接口。
|
||||
|
||||
## 方案
|
||||
|
||||
1. 在 admin/ 初始化 Go 1.23+ 模块,采用既定 gin / SQLite 技术栈和可测试的应用入口。
|
||||
2. 提供健康检查端点及对应单元测试;启动时错误必须显式返回,不吞错。
|
||||
3. 建立后续可扩展但保持最小的目录结构,不提前实现登录、任务、授权、提交订单或支付逻辑。
|
||||
4. 运行 go test ./... 与 go vet ./...,并记录真实命令。
|
||||
5. T-001 作为本轮共享文档所有者,只更新 admin 相关的 docs/03-tech-stack.md、docs/00-ai-start-here.md、docs/current-state.md 段落;不得代写尚未验证的 client 命令。
|
||||
|
||||
## 验收要点
|
||||
|
||||
- admin/go.mod 存在,Go 模块与依赖可解析。
|
||||
- 健康检查端点可通过 httptest 验证状态码和稳定响应。
|
||||
- go test ./...、go vet ./... 在 admin/ 通过。
|
||||
- 共享文档中的 admin 占位命令替换为经实际运行的命令,client 未完成部分仍明确标为未初始化。
|
||||
- 不包含采购业务、真机页面判据、下单或付款代码。
|
||||
|
||||
## 执行记录
|
||||
|
||||
### 2026-08-03T10:11:42Z · ila
|
||||
|
||||
T-001 实施与主审完成(2026-08-03):新增 admin Go 1.23 模块、gin 路由、GET /healthz、mattn/go-sqlite3 封装及 2 项离线测试;未实现业务路由、真机判据、下单或付款。主 agent 逐文件审查并独立运行:go mod tidy;gofmt -d .(无差异);go test -count=1 -race ./...(2 项通过);go vet ./...;go build ./...;python scripts/validate_agent_context.py;init.ps1(按 T-002 未完成预期退出 3)。已更新 admin 的真实运行/验证文档。风险:go-sqlite3 依赖 CGO;init.ps1 的“仓库只有文档”旧提示留给 T-003 修正。
|
||||
<!-- END VIKUNJA EXPORT -->
|
||||
|
||||
## 边界
|
||||
|
||||
- 不实现管理员登录、任务 CRUD、授权、围栏、真机自动化或任何 Phase 1+ 功能。
|
||||
- 不新增或修改采购业务 API;健康检查不承载业务数据。
|
||||
- 不编写任何下单、提交订单、付款或支付相关代码。
|
||||
- 不修改 `client/`、`docs/api.md`、`docs/04-architecture.md`、需求或交互原型。
|
||||
- 共享文档只写已经实际运行验证的 admin 命令,不代写尚未完成的 client 命令。
|
||||
@@ -0,0 +1,58 @@
|
||||
---
|
||||
id: T-002
|
||||
title: 初始化采购工具 client Python 骨架
|
||||
phase: 0
|
||||
deps: []
|
||||
status: DOING
|
||||
created: 2026-08-03
|
||||
vikunja_task_id: 17
|
||||
context_ref: a63bdb8
|
||||
work_branch: task/t-002-client-skeleton
|
||||
needs_device: false
|
||||
needs_human_review: false
|
||||
write_paths:
|
||||
- docs/tasks/T-002.md
|
||||
- client/**
|
||||
---
|
||||
|
||||
<!-- BEGIN VIKUNJA EXPORT id=17 synced=2026-08-03T09:57:46Z sha256=14061aa9aca2b854afd2b657c051f593e29272ed67327d0c840d499708f8c821 -->
|
||||
## 问题 / 背景
|
||||
|
||||
仓库尚无 client/requirements.txt 和可运行的采购工具代码,init.ps1 因此按预期以退出码 3 停止。没有稳定的 Python 包、测试入口、日志与产物策略,后续 ADB/uiautomator2 真机取证无法开始。
|
||||
|
||||
## 关联需求与交互
|
||||
|
||||
- 功能:Phase 0 工程地基,不实现采购执行流程。
|
||||
- 用户故事 / 交互:不适用,本任务只建立应用骨架,不实现已确认原型的生产界面。
|
||||
- 架构 / API:遵循 docs/03-tech-stack.md 与 TaskSource / ResultSink 边界;不新增跨端接口。
|
||||
|
||||
## 方案
|
||||
|
||||
1. 在 client/ 建立 Python 3.11+ 包、requirements.txt、应用入口和最小测试结构。
|
||||
2. 声明既定 PySide6 / uiautomator2 依赖,但基础单元测试不得要求连接真机。
|
||||
3. 建立日志和运行产物目录策略;本地虚拟环境与运行产物必须被 client 范围内的忽略规则排除,日志不得记录 token、地址、手机号或支付信息。
|
||||
4. 保证 python -m unittest discover -s tests -t . 与 python -m compileall -q src tests 可运行。
|
||||
5. T-001 关闭前不修改共享文档。先在 Vikunja 评论记录已验证的 client 命令;T-001 完成后由任务所有者先提交 write_paths 扩展,再更新共享文档并关闭 T-002。
|
||||
|
||||
## 验收要点
|
||||
|
||||
- client/requirements.txt、src 包、应用入口和 tests 存在。
|
||||
- unittest 与 compileall 在 client/ 通过,且不需要真机。
|
||||
- 日志 / 产物路径和脱敏边界有测试或可验证配置。
|
||||
- 不包含拼多多页面判据、真机自动化实现、下单或付款代码。
|
||||
- 关闭 T-002 前,共享文档中的 client 占位命令已由扩展后的独占 write_paths 更新为实际命令。
|
||||
|
||||
## 执行记录
|
||||
|
||||
(暂无)
|
||||
<!-- END VIKUNJA EXPORT -->
|
||||
|
||||
## 边界
|
||||
|
||||
- 不实现拼多多页面判据、ADB/uiautomator2 真机流程、采购执行 UI 或任何 Phase 1+ 功能。
|
||||
- 不编写任何进入订单确认、提交订单、付款或支付相关代码。
|
||||
- 不修改 `admin/`、`docs/api.md`、`docs/04-architecture.md`、需求或交互原型。
|
||||
- T-001 为 `DOING` 时不修改共享文档;先在 Vikunja 记录验证命令。T-001 关闭后,必须先提交
|
||||
`write_paths` 扩展,再更新 `docs/03-tech-stack.md`、`docs/00-ai-start-here.md`、
|
||||
`docs/current-state.md`,不得绕过路径门禁。
|
||||
- 本地虚拟环境、缓存、日志和运行产物不得提交 Git;凭据和敏感信息不得进入日志。
|
||||
@@ -0,0 +1,70 @@
|
||||
---
|
||||
id: T-009
|
||||
title: 明确 MVP 关键路径与并行开发波次
|
||||
phase: 0
|
||||
deps: [T-008]
|
||||
status: DONE
|
||||
created: 2026-08-03
|
||||
vikunja_task_id: 16
|
||||
context_ref: 31b08d7
|
||||
work_branch: task/t-009-mvp-critical-path
|
||||
needs_device: false
|
||||
needs_human_review: false
|
||||
write_paths:
|
||||
- docs/tasks/T-009.md
|
||||
- docs/00-ai-start-here.md
|
||||
- docs/06-tasks.md
|
||||
- docs/current-state.md
|
||||
---
|
||||
|
||||
<!-- BEGIN VIKUNJA EXPORT id=16 synced=2026-08-03T09:46:54Z sha256=e2541b909a42349c6459a241adbe4776dc518d93c599348846ee9f3a6413250c -->
|
||||
## 问题 / 背景
|
||||
|
||||
T-005 至 T-008 已完成,但当前状态快照仍把 T-005、T-006 写成等待人工确认;入口文档的阶段顺序也容易被理解为必须完成整个 Phase 0 后才能开始 Phase 1。现有依赖实际上允许 T-002 完成后立即进入 T-101,并让 T-004 与真机取证并行。若不把这条关键路径写入仓库,后续 agent 会按阶段串行执行,延迟 MVP 生死线 T-103。
|
||||
|
||||
## 关联需求与交互
|
||||
|
||||
- 功能:不适用,本任务只调整开发执行计划。
|
||||
- 用户故事:不适用。
|
||||
- 交互:不适用,不修改产品界面。
|
||||
- 架构 / API:不修改架构、API、数据模型或安全边界。
|
||||
|
||||
## 方案
|
||||
|
||||
1. 更新 docs/current-state.md:同步 T-005 至 T-008 已完成,明确 T-001 与 T-002 为下一波并行任务,并注明 T-002 完成后立即启动 T-101。
|
||||
2. 更新 docs/06-tasks.md:新增依赖驱动的并行波次和关键路径,明确 T-003、T-004、T-101 的可并行关系,并把 T-103 标为最高优先级生死线。
|
||||
3. 更新 docs/00-ai-start-here.md:把笼统的阶段串行表述改为依赖驱动;保留 T-103 结论出来前不写依赖真机字段的 Phase 2 生产页面。
|
||||
4. 所有共享文档由 T-009 所有者单独写入,不创建 T-001/T-002,不修改既有任务依赖与产品范围。
|
||||
|
||||
## 验收要点
|
||||
|
||||
- 三份文档对当前状态、下一步和关键路径的表述一致。
|
||||
- 明确 T-001/T-002 并行;T-002 后立即 T-101→T-103;T-001 后可做 T-004;T-001/T-002 后可做 T-003。
|
||||
- 明确 Phase 2 生产页面仍受 T-103 真机结论门禁约束。
|
||||
- 不修改 docs/02-requirements.md、docs/04-architecture.md、docs/api.md、docs/05-coding-rules.md。
|
||||
- python scripts/validate_agent_context.py 与 git diff --check 通过。
|
||||
|
||||
## 执行记录
|
||||
|
||||
### 2026-08-03T09:42:43Z · ila
|
||||
|
||||
T-009 已落成:Vikunja 任务 #16 与 Git 本地任务文件已建立,T-008 precedes T-009 依赖已配置。任务定义提交:4948afc。基线结果:validate_agent_context.py 通过;init.ps1 按预期以退出码 3 提示 T-001/T-002 尚未初始化。
|
||||
|
||||
### 2026-08-03T09:44:49Z · ila
|
||||
|
||||
T-009 实施完成(2026-08-03):更新 docs/00-ai-start-here.md、docs/06-tasks.md、docs/current-state.md,明确 T-001/T-002 并行、T-002 后立即 T-101→T-103、T-001 后 T-004、两端骨架后 T-003,并保留 T-103 前禁止开发依赖真机字段的 Phase 2 生产页面。验证:文档断言通过;python scripts/validate_agent_context.py 通过;git diff --check 通过;requirements/architecture/api/coding-rules 均未修改。
|
||||
|
||||
### 2026-08-03T09:46:42Z · ila
|
||||
|
||||
复核发现 @0xk3vin/vikunja-mcp@1.1.1 的 tasks_update 在仅提交 done/percentDone 时会清空 description。已使用包含原 description 的完整更新载荷恢复正文并保持 done=true;后续通过此 MCP 更新任务时必须携带现有 description,不能把部分更新当作 PATCH。该发现不改变本任务产品范围。
|
||||
<!-- END VIKUNJA EXPORT -->
|
||||
|
||||
## 边界
|
||||
|
||||
- 不修改 `docs/02-requirements.md`、`docs/04-architecture.md`、`docs/api.md`、
|
||||
`docs/05-coding-rules.md`、交互原型或生产代码。
|
||||
- 不改变既有任务依赖、MVP 产品范围、两趟执行流程、三道价格闸门或提交订单四条件。
|
||||
- 不以并行提速为理由跳过 T-103 真机取证,或在 T-103 结论前开发依赖真机字段的
|
||||
Phase 2 生产页面。
|
||||
- 不在本任务创建或领取 T-001、T-002;本任务只把已确认的执行策略写入权威文档。
|
||||
- 不让受托执行者修改本任务列出的共享文档;这些文件只由 T-009 所有者写入。
|
||||
Reference in New Issue
Block a user