Status: proposed
Date: 2025-07-18
The teamwork CLI manages .teamwork/ protocol files (config, state, handoffs, memory) that are read and written by both agents and humans. Corrupt or incomplete files cause silent failures downstream — a malformed state file can crash teamwork status, and a missing config field can cause teamwork start to produce invalid workflows. There is no way to check the health of .teamwork/ as a whole.
We need a teamwork validate command that checks structural integrity of all protocol files and reports problems clearly, so users can run it after init, before committing, or in CI.
We will implement teamwork validate as a new cobra command in cmd/teamwork/cmd/validate.go with a supporting internal/validate package that contains all validation logic.
- Default: Human-readable, one line per check result. Passing checks print
✓ <description>. Failing checks print✗ <description>: <reason>. A summary line at the end:N passed, M failed. --jsonflag: Structured JSON output (array of check results withpath,check,passed,messagefields) for CI and tooling consumption.--quietflag: Suppress passing checks; only show failures and the summary. Combine with--jsonfor machine-parseable CI output.
0— All checks pass.1— One or more validation checks failed.2— Validate itself could not run (e.g.,.teamwork/directory does not exist, I/O error reading directory listing). This is distinct from "checks failed" so CI can distinguish between "unhealthy project" and "broken tool invocation."
- Config exists —
.teamwork/config.yamlmust exist and be readable. - Config parses — File must parse as valid YAML.
- Config required fields —
project.namenon-empty,project.reponon-empty,roles.corenon-empty slice. - State files parse — Each
*.yamlin.teamwork/state/(recursive) must parse as valid YAML. - State required fields — Each state file must have:
id(non-empty string),type(non-empty string),status(one ofactive,blocked,completed,failed,cancelled),current_step(integer ≥ 0),created_at(non-empty string). - Handoff files non-empty — Each
*.mdin.teamwork/handoffs/(recursive) must exist and have size > 0. No content parsing in v1. - Memory files parse — Each
*.yamlin.teamwork/memory/must parse as valid YAML if file size > 0. Empty files (created byinit) are valid.
Checks 1–3 are fatal: if config is missing or unparseable, remaining checks still run (validate is not fail-fast — it reports all problems in one pass).
Create internal/validate/validate.go with:
type Result struct {
Path string `json:"path"`
Check string `json:"check"`
Passed bool `json:"passed"`
Message string `json:"message,omitempty"`
}
func Run(dir string) ([]Result, error)
Run returns all check results (both passing and failing). It returns an error only for category-2 failures (cannot run at all). Individual check failures are captured in Result entries with Passed: false.
The command file cmd/teamwork/cmd/validate.go handles flag parsing, calls validate.Run(), formats output, and sets the exit code.
- Use
config.Load()for checks 1–3. IfLoad()returns an error, report it. If it succeeds, inspect the returned*Configstruct for required-field checks. - Use
state.LoadAll()for check 4, but sinceLoadAllstops on first error, the validate package should implement its own walk that continues past errors to report all problems. - Do not use
memory.LoadCategory()directly — it silently returns empty structs for missing files. The validate package should do its ownReadFile+yaml.Unmarshalto distinguish "file missing" from "file empty" from "file invalid." - Handoff validation is file-stat only (size > 0), no package dependency needed.
-
internal/validate/validate.go— TheRun(dir string) ([]Result, error)function plus aResultstruct. Walk.teamwork/subdirectories, apply each check, collect results. Usegopkg.in/yaml.v3for YAML parsing (already a dependency). Usestate.StatusActiveetc. constants for status validation. Keep the valid-status set as amap[string]boolbuilt from the state package constants. -
internal/validate/validate_test.go— Unit tests usingt.TempDir(). Create valid and invalid.teamwork/structures, callRun(), assert expected results. Key cases: missing config, malformed YAML, missing required fields, valid state with each status, empty memory file, non-empty invalid memory file, empty handoff file. -
cmd/teamwork/cmd/validate.go— Cobra command following the existing pattern:var validateCmd,func init() { rootCmd.AddCommand(validateCmd) },func runValidate(...). Add--jsonand--quietbool flags. Format output, setos.Exit(1)for failures oros.Exit(2)for run errors. -
Update
docs/cli.md— Addteamwork validatesection betweeninitandstart, documenting flags and exit codes.
Validation messages should include the file path relative to the project root and a specific description:
✗ .teamwork/config.yaml: missing required field "project.name"
✗ .teamwork/state/feature/42.yaml: invalid status "running" (expected one of: active, blocked, completed, failed, cancelled)
✓ .teamwork/memory/patterns.yaml: valid YAML
- Positive: Users and CI can verify
.teamwork/integrity in one command. Errors are surfaced before they cause runtime failures instart,status, ornext. JSON output enables tooling integration. - Positive: Separate
internal/validatepackage keeps validation logic testable and decoupled from cobra. - Negative: The validate package duplicates some file-walking logic that exists in
state.LoadAll()andmemory.LoadCategory(). This is intentional — validate needs continue-on-error semantics that those functions don't provide. - Neutral: The
--jsonflag establishes a structured output precedent. Other commands may want this later, but we are not building a shared output-formatting layer now. That can be extracted if/when a second command needs it.
| Alternative | Why It Was Rejected |
|---|---|
Validate inside config.Load() / state.Load() as a Validate() method on each struct |
Spreads validation across packages, makes it hard to get a unified report. Also conflates "load" errors with "invalid content" errors. Validate needs holistic, continue-on-error behavior. |
| Fail-fast on first error | Users would need to run validate repeatedly to find all problems. One-pass reporting is more useful, especially in CI. |
Only human-readable output (no --json) |
CI pipelines and editors benefit from structured output. Low implementation cost to support both. |
| Exit code 1 for both "checks failed" and "cannot run" | Makes it impossible for CI to distinguish between "your project has issues" and "the tool is misconfigured." Two distinct codes are standard practice (cf. go vet, shellcheck). |