Title
internal/cli: subcommand dispatch, flags, exit codes
Summary
Implement the CLI shell from DESIGN.md §4: subcommand routing for
daily|standup|init|doctor|sources|version, per-subcommand flag.FlagSets
with the documented flags, usage/help text, config loading + validation
wiring, and the exit-code contract — with each subcommand body still a stub.
Context
Issues 25–29 fill in the subcommand bodies; this issue gives them a uniform
harness (parsed flags + loaded config + resolved report date handed to a
run function) so wiring stays consistent and testable. version becomes
fully functional here.
Scope
internal/cli/cli.go (dispatch, shared flag struct, usage),
version.go (complete), daily.go, standup.go, init.go, doctor.go,
sources.go (stubs returning "not implemented" errors), tests.
- Rewrite
cmd/worklog/main.go to call cli.Main(os.Args[1:], os.Stdout, os.Stderr) int and exit with its return value.
Detailed Requirements
cli.Main contract: pure function of args + writers (no os.Exit
inside; returns the exit code) so tests can drive it end-to-end.
- Flags exactly per DESIGN §4:
- shared:
--config, --date, --lang, --verbose
(on daily|standup|doctor|sources);
daily|standup only: --out, --save, --force, --no-llm,
--redact (values on|off only), --source (comma list, each value
must be one of the four source ids AND present in sources.enabled;
violations = usage error);
init only: --config, --force.
- Behavior in this issue:
worklog with no args, -h, --help, help → usage to stderr
listing subcommands + one-line descriptions; exit 2 for no-args/unknown,
0 for explicit help.
- Unknown subcommand →
unknown subcommand "x" + usage, exit 2.
- Flag parse error → FlagSet error + subcommand usage, exit 2.
- For
daily|standup|doctor|sources: load config (config.Load), print
unknown-key warnings to stderr as warning: unknown config key: <key>,
run Validate() — any error → all errors to stderr, exit 2. Then parse
--date (via timeutil, in the configured timezone) — invalid → exit 2.
Then call the stub, which returns an error → message to stderr, exit 1.
(Stub exit-1 is a TEMPORARY scaffolding exception to the DESIGN §13
exit-code contract, removed as issues 25/26/28/29 replace the stubs;
mark each stub with a // TODO(issue NN) comment.)
version: prints worklog <Version> (<Commit>) (from
internal/version, values "dev"/"none" when unset) to stdout, exit 0.
--lang overrides general.language; --redact overrides
redaction.mode; --no-llm overrides llm.enabled; overrides are
applied to the in-memory config copy handed to subcommands.
- Diagnostics discipline (DESIGN §4): stubs and all harness messages write
only to stderr; stdout is reserved for reports/version output.
--verbose stores a flag on the context struct passed to subcommands
(used later); no logging framework.
Acceptance Criteria
Validation
go test -race -cover ./internal/cli/ plus a manual transcript in the PR:
worklog, worklog help, worklog version, worklog daily,
worklog daily --date 2026-07-10 against an empty HOME (expected stub
error, exit 1).
Dependencies
01, 05 (config), 04 (date parsing).
Non-goals
Real subcommand behavior (25–29), output writing (22), doctor checks (28).
Design References
Source of truth: docs/issues/07-cli-skeleton.md (PR #1, branch docs/v1-design). If this issue and the repo docs disagree, the docs win. Execution order and dependencies: docs/ISSUE_PLAN.md (this is issue 07 of 33).
Title
internal/cli: subcommand dispatch, flags, exit codes
Summary
Implement the CLI shell from DESIGN.md §4: subcommand routing for
daily|standup|init|doctor|sources|version, per-subcommandflag.FlagSetswith the documented flags, usage/help text, config loading + validation
wiring, and the exit-code contract — with each subcommand body still a stub.
Context
Issues 25–29 fill in the subcommand bodies; this issue gives them a uniform
harness (parsed flags + loaded config + resolved report date handed to a
runfunction) so wiring stays consistent and testable.versionbecomesfully functional here.
Scope
internal/cli/cli.go(dispatch, shared flag struct, usage),version.go(complete),daily.go,standup.go,init.go,doctor.go,sources.go(stubs returning "not implemented" errors), tests.cmd/worklog/main.goto callcli.Main(os.Args[1:], os.Stdout, os.Stderr) intand exit with its return value.Detailed Requirements
cli.Maincontract: pure function of args + writers (noos.Exitinside; returns the exit code) so tests can drive it end-to-end.
--config,--date,--lang,--verbose(on
daily|standup|doctor|sources);daily|standuponly:--out,--save,--force,--no-llm,--redact(valueson|offonly),--source(comma list, each valuemust be one of the four source ids AND present in
sources.enabled;violations = usage error);
initonly:--config,--force.worklogwith no args,-h,--help,help→ usage to stderrlisting subcommands + one-line descriptions; exit 2 for no-args/unknown,
0 for explicit help.
unknown subcommand "x"+ usage, exit 2.daily|standup|doctor|sources: load config (config.Load), printunknown-key warnings to stderr as
warning: unknown config key: <key>,run
Validate()— any error → all errors to stderr, exit 2. Then parse--date(via timeutil, in the configured timezone) — invalid → exit 2.Then call the stub, which returns an error → message to stderr, exit 1.
(Stub exit-1 is a TEMPORARY scaffolding exception to the DESIGN §13
exit-code contract, removed as issues 25/26/28/29 replace the stubs;
mark each stub with a
// TODO(issue NN)comment.)version: printsworklog <Version> (<Commit>)(frominternal/version, values "dev"/"none" when unset) to stdout, exit 0.--langoverridesgeneral.language;--redactoverridesredaction.mode;--no-llmoverridesllm.enabled; overrides areapplied to the in-memory config copy handed to subcommands.
only to stderr; stdout is reserved for reports/
versionoutput.--verbosestores a flag on the context struct passed to subcommands(used later); no logging framework.
Acceptance Criteria
cli.Main: no args→2,--help→0, unknownsubcommand→2,
daily --date bad→2,daily(stub)→1,version→0,daily --redact maybe→2,daily --source nope→2,daily --savewithout configured output directory→2 (checked here in the harness,
per DESIGN §13).
versionprints exactly one line to stdout; nothing to stderr.key + one invalid value; assert stderr contents and exit code).
gofmt/vet/lint green; no new dependencies.Validation
go test -race -cover ./internal/cli/plus a manual transcript in the PR:worklog,worklog help,worklog version,worklog daily,worklog daily --date 2026-07-10against an empty HOME (expected stuberror, exit 1).
Dependencies
01, 05 (config), 04 (date parsing).
Non-goals
Real subcommand behavior (25–29), output writing (22), doctor checks (28).
Design References
docs/DESIGN.md§4, §13Source of truth:
docs/issues/07-cli-skeleton.md(PR #1, branchdocs/v1-design). If this issue and the repo docs disagree, the docs win. Execution order and dependencies:docs/ISSUE_PLAN.md(this is issue 07 of 33).