Skip to content

feat(quest): error on --depends-on "" instead of silent no-op - #73

Merged
kunallanjewar merged 6 commits into
mathomhaus:mainfrom
mvanhorn:feat/issue-47-empty-depends-on-error
Aug 18, 2026
Merged

feat(quest): error on --depends-on "" instead of silent no-op#73
kunallanjewar merged 6 commits into
mathomhaus:mainfrom
mvanhorn:feat/issue-47-empty-depends-on-error

Conversation

@mvanhorn

@mvanhorn mvanhorn commented May 5, 2026

Copy link
Copy Markdown
Contributor

Closes #47

State of play before this PR

The --clear-depends-on flag and its MCP equivalent (clear_depends_on=true) are already shipped in v0.1.0 (commit ad72f16). internal/quest/update_cmd.go registers the cobra flag and threads it through to UpdateParams.ClearDependsOn, and internal/quest/update.go does the right thing when it's set. Acceptance criteria #1, #2, and #5 from the issue are met by that existing code.

What's not yet handled is acceptance criterion #3 — the empty-string ambiguity:

Today guild quest update --depends-on "" is ambiguous — empty-string is treated inconsistently between the CLI and MCP surfaces, and there's no dedicated "drop all dependencies" verb. Agents reach for --replace-depends-on "" or similar and it silently no-ops.

The trim/skip loop at the top of Update() was dropping "" entries and reporting success without touching the dependency list. From an agent's view, "I tried to clear deps and the call succeeded" was indistinguishable from "I made no change."

What this PR does

1. Explicit error for --depends-on "" (internal/quest/update.go)

Added a guard right after the existing append-vs-replace conflict check: when DependsOn is non-empty but every entry trims to empty AND no Clear* / Replace* flag is set, Update() now returns the new ErrEmptyDependsOn:

depends_on contains only empty values: use --clear-depends-on to remove all dependencies

A mixed slice (["QUEST-1", ""]) deliberately keeps the trim/skip behavior so existing callers that incidentally pass an empty entry alongside a real ID are not broken. Only the all-empty shape gets redirected.

2. Explicit before=2 / after=0 test for --clear-depends-on (AC #4)

The existing TestUpdate_AutoUnblock_ClearDependsOnPath covers the auto-unblock side-effect for before=1 -> after=0. This PR adds TestUpdate_ClearDependsOn_BeforeTwoAfterZero as the literal shape called out in the acceptance criteria — a quest with two deps, --clear-depends-on drops both.

3. Tests for the new error path

TestUpdate_EmptyDependsOn_RedirectsToClear runs the new guard against three inputs (single empty, single whitespace, multiple empty), asserts the call returns ErrEmptyDependsOn via errors.Is, and verifies the dependency list is untouched on error. A trailing assertion confirms the mixed-slice case still goes through.

Files

Verification

  • go test ./...: 1190 tests pass across 20 packages
  • gofmt -l ./internal/quest/...: clean
  • go vet -tags testing -unsafeptr=false ./internal/quest/...: clean
  • go build ./...: clean

Out of scope

Not changing the --depends-on / --replace-depends-on interaction beyond the empty-string redirect. The existing conflict logic at line 63 already errors when both append and replace forms are set with non-empty content; that path is unchanged.

mvanhorn added 2 commits May 3, 2026 23:25
… as alias

Closes mathomhaus#69.

internal/quest/epic_cmd.go registers the rename verb with
CLIPath: []string{"quest", "campaign"} and CLIAliases: []string{"epic"},
and the comment at lines 25-27 is explicit that 'quest campaign' is the
canonical command and 'quest epic' is the alias. CAMPAIGNS.md was treating
'quest epic' as the primary verb, which steered users away from the
canonical spelling.

Flip the verb naming in docs/CAMPAIGNS.md to mirror CLIPath / CLIAliases:

- Line 60: 'quest campaign' is the rename verb, 'quest epic' as alias.
- Line 69 (bash example): 'guild quest campaign ...' with a comment noting
  'quest epic' works as an alias.
- Lines 72-74 (prose): 'quest campaign' canonical, 'quest epic' alias,
  MCP tool name 'quest_epic' unchanged for backward compat.
- Line 84 (out-of-scope section): mention 'quest campaign' rather than
  'quest epic'.

No code or MCP tool name changes; CLI dual-naming is intentional.
Closes mathomhaus#47 (acceptance criterion mathomhaus#3 + explicit before=2/after=0 test)

Note: --clear-depends-on, the MCP `clear_depends_on` parameter, and the
underlying ClearDependsOn flow are already wired in v0.1.0 (see
internal/quest/update_cmd.go and the existing ClearDependsOn unit tests
in update_test.go). What was missing from the issue's acceptance list:

- AC mathomhaus#3: `guild quest update --depends-on ""` was a silent no-op. The
  trim/skip loop in Update() dropped the empty entry and reported
  success without touching the dependency list, which is exactly the
  ambiguity the issue calls out (especially for agents).

  This PR adds an explicit guard: when DependsOn is non-empty but
  every entry trims to empty AND no Clear/Replace flag is set,
  Update() now returns ErrEmptyDependsOn with a message pointing the
  caller at --clear-depends-on.

  A mixed slice ([real-id, ""]) keeps the trim/skip behavior so
  existing callers that incidentally pass an empty entry alongside
  real IDs are not broken.

- AC mathomhaus#4: explicit `before=2 -> after=0` test added as
  TestUpdate_ClearDependsOn_BeforeTwoAfterZero. The pre-existing
  TestUpdate_AutoUnblock_ClearDependsOnPath only covers
  before=1->after=0, so this fills the named acceptance shape.

The new error path is covered by TestUpdate_EmptyDependsOn_RedirectsToClear
across single-empty / single-whitespace / multiple-empty inputs, and
asserts state is untouched on error.

Touched files:
- internal/quest/types.go: new ErrEmptyDependsOn sentinel + doc comment
- internal/quest/update.go: empty-depends-on guard, ~12 lines after the
  existing conflict check
- internal/quest/update_test.go: 2 new tests, 4 new sub-tests

`go test ./...`: 1190 tests pass, 20 packages. `gofmt -l` clean.
`go vet -tags testing -unsafeptr=false ./...` clean.
@github-actions github-actions Bot added area: quest Quest board / task coordination area: docs Documentation, README, INSTRUCTIONS labels May 5, 2026
mvanhorn and others added 3 commits May 5, 2026 02:49
…check

CI lint failed on update_test.go:332 with:

  Function `mustLoad` should pass the context parameter (contextcheck)

`mustLoad` synthesizes its own context.Background() internally, which
contextcheck flags when called from inside a subtest closure that has
a parent `ctx` in scope. The other mustLoad call sites in this file
are at the test-function top level where contextcheck is lenient; the
subtest closure scope tripped the check on the new
TestUpdate_EmptyDependsOn_RedirectsToClear cases.

Switching that one call to Load(ctx, db, pid, b.ID) directly threads
the parent ctx through, which is what contextcheck wants. Behavior is
identical (mustLoad is a 5-line wrapper around Load).
The error told the caller to use --clear-depends-on, which is the CLI
spelling of a field the registry calls clear_depends_on. Most callers hit
this over MCP, where the flag form does not exist, so the recovery hint
pointed at something they cannot set. Name the field first and keep the
CLI spelling as the parenthetical.
@kunallanjewar
kunallanjewar marked this pull request as ready for review August 18, 2026 16:59
@github-actions github-actions Bot removed the area: docs Documentation, README, INSTRUCTIONS label Aug 18, 2026
@kunallanjewar

Copy link
Copy Markdown
Contributor

Picking this up so it can land. What I did on the branch:

  • Synced with main (it was based on a commit from early May). No conflicts. The docs/CAMPAIGNS.md commit dropped out of the diff because that same fix landed upstream separately in the meantime, so what remains is exactly the depends-on change: 96 added lines across types.go, update.go, and update_test.go.
  • Adjusted the error string to name the MCP field first: set clear_depends_on to remove all dependencies (--clear-depends-on on the CLI). The original named only the CLI flag, and most callers hitting this path are agents on the MCP surface where that flag spelling does not exist. An error that names something the caller cannot set is a dead end for the exact audience the guard is for.

The design is right. A silent no-op on --depends-on \"\" is indistinguishable from success, and the mixed-slice carve-out (real ID alongside an empty entry keeps trim-and-skip) means no existing caller changes behavior. Full pre-commit gate green locally: fmt, vet, lint, sqlcheck, race tests, generated-docs drift.

Marked ready for review. Thanks for the writeup in the description, the state-of-play section made this quick to verify.

@kunallanjewar
kunallanjewar merged commit ec77e88 into mathomhaus:main Aug 18, 2026
13 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area: quest Quest board / task coordination

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[feature] quest update: support clearing all dependencies via --clear-depends-on flag

2 participants