docs: Remove sibling product and lab path leaks

Outsiders reading this module should not see private sibling
names (tradey), unfinished rewire notes, or /fast/projects
lab paths. Keep the library self-contained in README, design,
plan, and test fixtures/identity strings.
This commit is contained in:
s1d3sw1ped_bot
2026-09-01 19:43:35 +00:00
parent 6d02894e5f
commit 9e711957c0
7 changed files with 74 additions and 74 deletions
@@ -2,7 +2,7 @@
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** Ship a Go library at `/fast/projects/golang/robinhood-agentic-mcp` that presents every Robinhood Agentic MCP tool with tradeys transport/auth and Alpaca-shaped decimals/enums.
**Goal:** Ship a Go library that presents every Robinhood Agentic MCP tool with OAuth/token transport and Alpaca-shaped decimals/enums.
**Architecture:** One shared `client.Client` (streamable HTTP session, JSON-RPC fallback). Asset-class packages (`accounts`, `equity`, `options`, `crypto`, `watchlists`, `market`, `scanner`) wrap `client.Caller`. Root `rh` is a facade (`Connect`, `Login`, re-exported `Config` and enums). Tests hit `internal/rhntest`, never live Robinhood.
@@ -10,7 +10,7 @@
## Global Constraints
- Module path: `s1d3sw1ped/robinhood-agentic-mcp`. Go 1.25. Work only in `/fast/projects/golang/robinhood-agentic-mcp`. Do not modify tradey.
- Module path: `s1d3sw1ped/robinhood-agentic-mcp`. Go 1.25. Work only in this repository.
- Default MCP URL: `https://agent.robinhood.com/mcp/trading`.
- Identity: `Config.Name` / `Config.Version` set before `Login`/`Connect`. Empty → `robinhood-agentic-mcp` / `0.1.0`.
- Money/size/price: `decimal.Decimal` or `*decimal.Decimal`. Never `float64`. Encode with `Decimal.String()`. Unparseable JSON errors; omitted/null/`""`/`0` do not.
@@ -165,7 +165,7 @@ func TestEncode(t *testing.T) {
- [ ] **Step 2: Run test to verify it fails**
Run: `cd /fast/projects/golang/robinhood-agentic-mcp && go test ./internal/wire/ -count=1`
Run: `go test ./internal/wire/ -count=1`
Expected: FAIL module/package not found (create `go.mod` first if `go test` refuses, then FAIL undefined `wire`).
@@ -202,7 +202,7 @@ vet:
go vet ./...
```
`internal/wire/wire.go`: `Unwrap` reads optional `{"data": ...}` (same as tradey `unwrapData`). `Dec` accepts `nil` (zero, nil error), `float64`, `json.Number`, numeric `string`, empty string (zero, nil error); anything else errors with `fmt.Errorf("parse decimal: %v", v)`. `DecOpt`: `nil` or `""``(nil, nil)`; else `Dec` and return a pointer. `Encode` returns `d.String()`.
`internal/wire/wire.go`: `Unwrap` reads optional `{"data": ...}`. `Dec` accepts `nil` (zero, nil error), `float64`, `json.Number`, numeric `string`, empty string (zero, nil error); anything else errors with `fmt.Errorf("parse decimal: %v", v)`. `DecOpt`: `nil` or `""``(nil, nil)`; else `Dec` and return a pointer. `Encode` returns `d.String()`.
- [ ] **Step 4: Run tests and make sure they pass**
@@ -395,7 +395,7 @@ git commit -m "feat: add httptest Robinhood MCP mock"
- Produces: `type Client struct { URL, Token, Name, Version string; HTTP *http.Client; Hook Caller; session /* unexported */ }`, `func (c *Client) Call(ctx context.Context, name string, args map[string]any) (json.RawMessage, error)`
- If `Hook != nil`, return `Hook.Call`
- Else if `session != nil`, session path (implemented in Task 5)
- Else JSON-RPC POST as tradey `rpcCall`: `Content-Type: application/json`, `Accept: application/json, text/event-stream`, `Authorization: Bearer `+Token when Token != `""`
- Else JSON-RPC POST as `rpcCall`: `Content-Type: application/json`, `Accept: application/json, text/event-stream`, `Authorization: Bearer `+Token when Token != `""`
- HTTP ≥300, decode errors, and JSON-RPC `error``*ToolError` with `Name` set
- [ ] **Step 1: Write the failing test**
@@ -455,7 +455,7 @@ func TestClientCall_hook(t *testing.T) {
}
```
Copy tradeys `rpcCall` from `/fast/projects/golang/tradey/internal/broker/mcp.go` (`rpcReq`/`rpcResp`, POST `tools/call`) and wrap failures with `ToolErrorf(name, "%w", err)` or `ToolErrorf(name, "http %d: %s", code, body)`.
Implement `rpcCall` (`rpcReq`/`rpcResp`, POST `tools/call`) and wrap failures with `ToolErrorf(name, "%w", err)` or `ToolErrorf(name, "http %d: %s", code, body)`.
- [ ] **Step 2: Run test to verify it fails**
@@ -541,7 +541,7 @@ func TestLoginFromEnv(t *testing.T) {
t.Setenv("ROBINHOOD_ACCESS_TOKEN", "tok-live")
t.Setenv("ROBINHOOD_REFRESH_TOKEN", "ref")
path := filepath.Join(t.TempDir(), "tokens.json")
id, err := auth.Login(t.Context(), auth.Config{TokenFile: path, Name: "tradey", Version: "9"})
id, err := auth.Login(t.Context(), auth.Config{TokenFile: path, Name: "example-app", Version: "9"})
if err != nil {
t.Fatal(err)
}
@@ -563,14 +563,14 @@ func TestWithDefaults(t *testing.T) {
if c.URL != auth.DefaultURL || c.Name != auth.DefaultName || c.Version != auth.DefaultVersion {
t.Fatalf("%+v", c)
}
c = auth.Config{Name: "tradey", Version: "1.2.3", URL: "http://x"}.WithDefaults()
if c.Name != "tradey" || c.Version != "1.2.3" || c.URL != "http://x" {
c = auth.Config{Name: "example-app", Version: "1.2.3", URL: "http://x"}.WithDefaults()
if c.Name != "example-app" || c.Version != "1.2.3" || c.URL != "http://x" {
t.Fatalf("%+v", c)
}
}
```
Copy token JSON tags from `/fast/projects/golang/tradey/internal/broker/login.go` `TokenSet`.
Define token JSON tags on `TokenSet` (access, refresh, type, expiry, client_id/secret, auth/token/redirect URLs, account_id).
- [ ] **Step 2: Run test to verify it fails**
@@ -604,15 +604,15 @@ git commit -m "feat: add token file and env Login"
- Modify: `auth/login.go` (call `loginOAuth` instead of the placeholder error)
**Interfaces:**
- Consumes: tradey `/fast/projects/golang/tradey/internal/broker/oauth.go` and `connectSession` in `mcp.go`/`oauth.go`
- Produces: `loginOAuth(ctx, cfg Config) (accountID string, error)` using `cfg.Name` as MCP `Implementation.Name` **and** OAuth `ClientName`; callback body `cfg.Name+" is signed in. You can close this tab."`; `client.ConnectSession(ctx, url string, tok auth.TokenSet, name, version string) (*Client, error)` port of tradey `connectSession` (OAuth handler if ClientID+TokenURL present, else bearer transport)
- Consumes: Robinhood OAuth + MCP session connect requirements
- Produces: `loginOAuth(ctx, cfg Config) (accountID string, error)` using `cfg.Name` as MCP `Implementation.Name` **and** OAuth `ClientName`; callback body `cfg.Name+" is signed in. You can close this tab."`; `client.ConnectSession(ctx, url string, tok auth.TokenSet, name, version string) (*Client, error)` (OAuth handler if ClientID+TokenURL present, else bearer transport)
No browser-OAuth test. Identity wiring is tested by exporting `oauthIdentity(cfg Config) (name, version string)` (unexported is fine if tested in package `auth`, not `auth_test`):
```go
func TestOAuthIdentity(t *testing.T) {
gotN, gotV := oauthIdentity(Config{Name: "tradey", Version: "0.9"})
if gotN != "tradey" || gotV != "0.9" {
gotN, gotV := oauthIdentity(Config{Name: "example-app", Version: "0.9"})
if gotN != "example-app" || gotV != "0.9" {
t.Fatalf("%s %s", gotN, gotV)
}
gotN, gotV = oauthIdentity(Config{})
@@ -624,15 +624,15 @@ func TestOAuthIdentity(t *testing.T) {
`loginOAuth` must call `oauthIdentity(cfg.WithDefaults())` for Implementation.Name, ClientName, and the callback HTML.
Copy `loginOAuth` and `connectSession` / `bearerRT` / `tokenSetFrom` / `openBrowser` from tradey. Substitutions:
Implement `loginOAuth` and `connectSession` / `bearerRT` / `tokenSetFrom` / `openBrowser`. Identity rules:
- `"tradey"` Implementation.Name → `cfg.Name` (defaulted)
- Implementation.Name → `cfg.Name` (defaulted)
- `"0.1.0"` Version → `cfg.Version`
- `ClientName: "tradey"``cfg.Name`
- `"Tradey is signed in..."``cfg.Name+" is signed in. You can close this tab."`
- `"Open this URL to authorize Tradey with Robinhood"``"Open this URL to authorize "+cfg.Name+" with Robinhood"`
- `"robinhood session expired; run tradey login"``"robinhood session expired; run Login again"`
- After connect, `get_accounts` via a temporary `client.Client{session, Call: sessionCall}` is optional for account_id; port tradeys block that writes `saved.AccountID`. Do **not** enforce tradey `supported()` IRA/margin bans.
- `ClientName``cfg.Name`
- Callback copy`cfg.Name+" is signed in. You can close this tab."`
- Authorize prompt`"Open this URL to authorize "+cfg.Name+" with Robinhood"`
- Expired session`"robinhood session expired; run Login again"`
- After connect, `get_accounts` via a temporary `client.Client{session, Call: sessionCall}` is optional for account_id; persist `saved.AccountID` when present. Do **not** enforce app-level IRA/margin bans.
`Login`: env token first, else `loginOAuth`.
@@ -644,7 +644,7 @@ Run: `go test ./auth/ -count=1 -run TestOAuthIdentity`
Expected: FAIL undefined `oauthIdentity`
- [ ] **Step 3: Write the OAuth implementation** (`oauthIdentity` + copy tradey OAuth with the substitutions below)
- [ ] **Step 3: Write the OAuth implementation** (`oauthIdentity` + OAuth with the identity rules below)
- [ ] **Step 4: Run tests**
@@ -664,7 +664,7 @@ git commit -m "feat: add OAuth login and MCP session connect"
### Task 7: accounts package
**Files:**
- Create: `accounts/client.go`, `accounts/accounts.go`, `accounts/accounts_test.go`, `accounts/testdata/accounts.json` (copy `/fast/projects/golang/tradey/internal/broker/testdata/accounts.json`)
- Create: `accounts/client.go`, `accounts/accounts.go`, `accounts/accounts_test.go`, `accounts/testdata/accounts.json`
**Interfaces:**
- Consumes: `client.Caller`, `wire.Unwrap`, `wire.Dec` / `DecOpt`
@@ -765,7 +765,7 @@ git commit -m "feat: add accounts MCP methods"
| TechnicalIndicators | `get_equity_technical_indicators` | `Symbol`, `Type`, `Interval`, `StartTime time.Time` (required), `EndTime time.Time` (zero omits), `Bounds`, `AdjustmentType`, `Output`, `Period *int`, `NumStd *decimal.Decimal`, `FastPeriod *int`, `SlowPeriod *int`, `SignalPeriod *int`, `Multiplier *decimal.Decimal`, `Method` |
| News | `get_equity_news` | `Symbol`, `Limit int`, `Cursor` |
`Quote`: `Symbol string`, `Bid, Ask, Last, PrevClose, Volume decimal.Decimal`. Parse tradey envelope `{quotes:[{quote:{symbol,last_trade_price,bid_price,ask_price}, close:{symbol,price}}]}`.
`Quote`: `Symbol string`, `Bid, Ask, Last, PrevClose, Volume decimal.Decimal`. Parse Robinhood envelope `{quotes:[{quote:{symbol,last_trade_price,bid_price,ask_price}, close:{symbol,price}}]}`.
`Bar`: `Symbol`, `Time time.Time`, `Open, High, Low, Close, Volume decimal.Decimal`, `Interpolated bool`. Parse `{historicals:[{symbol, data_points:[{begins_at, open, high, low, close, volume, interpolated}]}]}`.
@@ -780,7 +780,7 @@ git commit -m "feat: add accounts MCP methods"
Assert `Last == 100`, `Bid == 99.9`, `PrevClose == 98`.
3. Historicals fixture from tradey parser: `begins_at` RFC3339, string OHLC.
3. Historicals fixture from the parser: `begins_at` RFC3339, string OHLC.
- [ ] **Step 2: Run test to verify it fails**
@@ -836,7 +836,7 @@ func (c *Client) PlaceOrder(ctx context.Context, req PlaceOrderRequest) (Order,
func (c *Client) CancelOrder(ctx context.Context, req CancelOrderRequest) error // account_number, order_id
```
Wire map (tradey `placeArgs`): `quantity`/`limit_price`/`stop_price`/`dollar_amount` as `wire.Encode`; `time_in_force` only if non-empty; `ref_id` and `idempotency_key` both set from `RefID` on Place (not Review); `type` = `string(req.Type)` so `client.Stop``"stop_market"`.
Wire map (`placeArgs`): `quantity`/`limit_price`/`stop_price`/`dollar_amount` as `wire.Encode`; `time_in_force` only if non-empty; `ref_id` and `idempotency_key` both set from `RefID` on Place (not Review); `type` = `string(req.Type)` so `client.Stop``"stop_market"`.
- [ ] **Step 1: Write the failing test**
@@ -1000,9 +1000,9 @@ type Leg struct {
| AddOption | `add_option_to_watchlist` |
| RemoveOption | `remove_option_from_watchlist` |
Parse lists with `title` falling back to `name` (tradey). Items keep `symbol` / `object_type` / nested `instrument.symbol`. Do not filter object types in v1 (tradeys equity-only filter stays in tradey).
Parse lists with `title` falling back to `name`. Items keep `symbol` / `object_type` / nested `instrument.symbol`. Do not filter object types in v1 (equity-only filters stay in the app).
- [ ] **Step 1: Failing tests** — stub table 12 tools; rhntest Lists `{"watchlists":[{"id":"wl-1","title":"TRADEY"}]}` and Items `{"items":[{"symbol":"MU","object_type":"equity"}]}`.
- [ ] **Step 1: Failing tests** — stub table 12 tools; rhntest Lists `{"watchlists":[{"id":"wl-1","title":"Tech"}]}` and Items `{"items":[{"symbol":"MU","object_type":"equity"}]}`.
- [ ] **Step 2:** `go test ./watchlists/ -count=1` FAIL
@@ -1086,7 +1086,7 @@ Filter/column request structs match the MCP properties (`filter_type`, `predicat
**Files:**
- Create: `rh.go`, `connect.go`, `tools.go`, `rh_test.go`, `testdata/tools.json`, `README.md`
- Modify: none of tradey
- Modify: this repository only
**Interfaces:**
```go
@@ -1120,7 +1120,7 @@ func RegisteredTools() []string // concat of every package Tools()
`Connect` test: rhntest with token file mode 0600 + `get_accounts` fixture; `Connect` succeeds via RPC fallback; `api.Equity.Quotes` hits the mock.
README: module path, DefaultURL, Config.Name, example `Login`/`Connect`/`Equity.PlaceOrder` with `decimal` + `rh.Limit`, warning that this moves real money, not investment advice, no CLI, tradey not wired yet.
README: module path, DefaultURL, Config.Name, example `Login`/`Connect`/`Equity.PlaceOrder` with `decimal` + `rh.Limit`, warning that this moves real money, not investment advice, no CLI.
- [ ] **Step 1: Write failing tests**
@@ -1151,7 +1151,7 @@ func TestConnect_rpcFallback(t *testing.T) {
if err := auth.WriteTokens(path, "tok", ""); err != nil {
t.Fatal(err)
}
api, err := rh.Connect(t.Context(), rh.Config{URL: s.URL, TokenFile: path, Name: "tradey"})
api, err := rh.Connect(t.Context(), rh.Config{URL: s.URL, TokenFile: path, Name: "example-app"})
if err != nil {
t.Fatal(err)
}
@@ -1198,7 +1198,7 @@ git commit -m "feat: add rh Connect facade and tool coverage"
| rhntest + per-method stub + round-trip | 3, 714 |
| tools.json coverage | 15 |
| No live MCP / no browser tests | 56, 15 |
| tradey untouched | all |
| this module stands alone | all |
| README | 15 |
No `TBD`/`TODO`. `Connect` is defined in Task 15 after subpackages exist; earlier tasks test via `pkg.New(stub|rpcClient)`.