refactor: turn the repo into a two-plugin marketplace
tea and tdl were two repositories, each carrying its own
.claude-plugin/marketplace.json — two marketplaces to register for what
is one collection. Fold them into one.
The repo root is now the marketplace and nothing else: a single
.claude-plugin/marketplace.json whose entries point at ./plugins/tea and
./plugins/tdl. A plugin's root is its own directory under plugins/, so
${CLAUDE_PLUGIN_ROOT} still resolves inside it and every path a plugin
uses stays relative to itself — the hooks and the test roots needed no
adjustment beyond the move.
tea's files move with git mv, so its history and blame follow. tdl
arrives as a plain copy; its history stays in claude-skills/threedotslab.
test_payload_root asserted `tmp/` was ignored by REPO/.gitignore. The
rule is that tmp/ is ignored, not which file says so, and git reads every
.gitignore on the way up — so the test now walks up to the repo root the
same way git does.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,124 @@
|
||||
# Query Handler Scaffold Template
|
||||
|
||||
Generate a query handler file with a read model interface.
|
||||
|
||||
## Placeholders
|
||||
|
||||
- `{{Name}}` — PascalCase query name (e.g., `AvailableHours`)
|
||||
- `{{name}}` — camelCase (e.g., `availableHours`)
|
||||
- `{{name_snake}}` — snake_case (e.g., `available_hours`)
|
||||
- `{{module}}` — Go module path from go.mod
|
||||
- `{{Result}}` — Result type (e.g., `[]Date`, `*HourDetails`)
|
||||
|
||||
## File: `app/query/{{name_snake}}.go`
|
||||
|
||||
```go
|
||||
package query
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
|
||||
"{{module_common}}/decorator"
|
||||
)
|
||||
|
||||
// Read model — defines what data the query needs
|
||||
// Implemented by adapters (repository or dedicated read store)
|
||||
type {{Name}}ReadModel interface {
|
||||
{{Name}}(ctx context.Context /* TODO: add query params */) ({{Result}}, error)
|
||||
}
|
||||
|
||||
// 1. Query struct — noun phrase, plain data
|
||||
type {{Name}} struct {
|
||||
// TODO: Add query parameters
|
||||
// Example:
|
||||
// From time.Time
|
||||
// To time.Time
|
||||
}
|
||||
|
||||
// Result types — optimized for reading, may differ from domain entities
|
||||
// type Date struct {
|
||||
// Date time.Time
|
||||
// Hours []Hour
|
||||
// }
|
||||
|
||||
// 2. Exported handler type alias
|
||||
type {{Name}}Handler decorator.QueryHandler[{{Name}}, {{Result}}]
|
||||
|
||||
// 3. Unexported concrete handler struct
|
||||
type {{name}}Handler struct {
|
||||
readModel {{Name}}ReadModel
|
||||
}
|
||||
|
||||
// 4. Constructor with nil-checks + decorator wrapping
|
||||
func New{{Name}}Handler(
|
||||
readModel {{Name}}ReadModel,
|
||||
logger *logrus.Entry,
|
||||
metricsClient decorator.MetricsClient,
|
||||
) {{Name}}Handler {
|
||||
if readModel == nil {
|
||||
panic("nil readModel")
|
||||
}
|
||||
if logger == nil {
|
||||
panic("nil logger")
|
||||
}
|
||||
if metricsClient == nil {
|
||||
panic("nil metricsClient")
|
||||
}
|
||||
|
||||
return decorator.ApplyQueryDecorators[{{Name}}, {{Result}}](
|
||||
{{name}}Handler{readModel: readModel},
|
||||
logger,
|
||||
metricsClient,
|
||||
)
|
||||
}
|
||||
|
||||
// Handle — delegates to read model, may add input validation
|
||||
func (h {{name}}Handler) Handle(ctx context.Context, q {{Name}}) ({{Result}}, error) {
|
||||
// TODO: Add input validation if needed
|
||||
// Example:
|
||||
// if q.From.After(q.To) {
|
||||
// return nil, errors.NewIncorrectInputError("date-from-after-date-to", "date from is after date to")
|
||||
// }
|
||||
|
||||
return h.readModel.{{Name}}(ctx /* TODO: pass query params */)
|
||||
}
|
||||
```
|
||||
|
||||
## Update `app/app.go`
|
||||
|
||||
Add to the `Queries` struct:
|
||||
|
||||
```go
|
||||
type Queries struct {
|
||||
// ... existing handlers ...
|
||||
{{Name}} query.{{Name}}Handler
|
||||
}
|
||||
```
|
||||
|
||||
## Update `service/application.go`
|
||||
|
||||
Wire the handler. The read model is typically implemented by the same repository adapter or a dedicated read adapter:
|
||||
|
||||
```go
|
||||
Queries: app.Queries{
|
||||
// ... existing handlers ...
|
||||
{{Name}}: query.New{{Name}}Handler(
|
||||
{{entity}}Repository, // implements {{Name}}ReadModel
|
||||
logger,
|
||||
metricsClient,
|
||||
),
|
||||
},
|
||||
```
|
||||
|
||||
## Implement ReadModel on Adapter
|
||||
|
||||
Add the read model method to your repository adapter:
|
||||
|
||||
```go
|
||||
// In adapters/
|
||||
func (r *Memory{{Entity}}Repository) {{Name}}(ctx context.Context /* params */) ({{Result}}, error) {
|
||||
// TODO: Implement query against storage
|
||||
}
|
||||
```
|
||||
Reference in New Issue
Block a user