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:
naudachu
2026-08-11 00:25:28 +05:00
parent 23f78beafb
commit 83f73c5cea
69 changed files with 4187 additions and 168 deletions
@@ -0,0 +1,115 @@
# Command Handler Scaffold Template
Generate a single command handler file following the 4-component pattern.
## Placeholders
- `{{Name}}` — PascalCase command name (e.g., `ScheduleTraining`)
- `{{name}}` — camelCase (e.g., `scheduleTraining`)
- `{{module}}` — Go module path from go.mod
- `{{entity}}` — Domain entity name, lowercase (e.g., `hour`)
- `{{Entity}}` — Domain entity name, PascalCase (e.g., `Hour`)
## File: `app/command/{{name_snake}}.go`
```go
package command
import (
"context"
"github.com/sirupsen/logrus"
"{{module}}/domain/{{entity}}"
"{{module_common}}/decorator"
)
// 1. Command struct — imperative verb + noun, plain data
type {{Name}} struct {
// TODO: Add command fields
// Example:
// UUID string
// Hour time.Time
}
// 2. Exported handler type alias
type {{Name}}Handler decorator.CommandHandler[{{Name}}]
// 3. Unexported concrete handler struct
type {{name}}Handler struct {
{{entity}}Repo {{entity}}.Repository
}
// 4. Constructor with nil-checks + decorator wrapping
func New{{Name}}Handler(
{{entity}}Repo {{entity}}.Repository,
logger *logrus.Entry,
metricsClient decorator.MetricsClient,
) {{Name}}Handler {
if {{entity}}Repo == nil {
panic("nil {{entity}}Repo")
}
if logger == nil {
panic("nil logger")
}
if metricsClient == nil {
panic("nil metricsClient")
}
return decorator.ApplyCommandDecorators[{{Name}}](
{{name}}Handler{{"{"}}{{entity}}Repo: {{entity}}Repo},
logger,
metricsClient,
)
}
// Handle — orchestrates domain logic, does NOT contain business rules
func (h {{name}}Handler) Handle(ctx context.Context, cmd {{Name}}) error {
// TODO: Implement command handling
//
// Typical patterns:
//
// Pattern A — Update via callback:
// return h.{{entity}}Repo.Update{{Entity}}(ctx, cmd.UUID, func(e *{{entity}}.{{Entity}}) (*{{entity}}.{{Entity}}, error) {
// if err := e.SomeDomainAction(); err != nil {
// return nil, err
// }
// return e, nil
// })
//
// Pattern B — Create new entity:
// entity, err := {{entity}}.New{{Entity}}(cmd.UUID, ...)
// if err != nil {
// return err
// }
// return h.{{entity}}Repo.Save(ctx, entity)
return nil
}
```
## Update `app/app.go`
After creating the handler, add it to the `Commands` struct:
```go
type Commands struct {
// ... existing handlers ...
{{Name}} command.{{Name}}Handler
}
```
## Update `service/application.go`
Wire the handler in the composition root:
```go
Commands: app.Commands{
// ... existing handlers ...
{{Name}}: command.New{{Name}}Handler(
{{entity}}Repository,
logger,
metricsClient,
),
},
```