Skip to content

feat(gardener/daemon): add sync --open-issues mode to gardener start #341

@serenakeyitan

Description

@serenakeyitan

Problem

gardener start daemon currently can only run the sync sweep in two modes:

  • default: gardener sync (detect only, no side effects)
  • --sync-apply: gardener sync --apply (opens one aggregated tree PR)

There is no way to configure the daemon to run gardener sync --open-issues, which is the mode that opens one tree issue per drift proposal (then breeze picks them up and dispatches gardener draft-node → per-proposal tree PR). This is the issue-first workflow we've been testing end-to-end since #277.

So the issue-first chain (source PR → tree issue → breeze → draft-node PR) cannot be kept running autonomously. The only option today is to run sync --open-issues by hand or via a side cron — the daemon doesn't help.

Proposal

Refactor the daemon sync-sweep flag into a single enum, replacing --sync-apply:

--sync-mode <detect|apply|open-issues>
  detect        Default. Run `gardener sync` only. No writes.
  apply         Run `gardener sync --apply`. One aggregated tree PR.
  open-issues   Run `gardener sync --open-issues`. One tree issue per drift
                proposal, assignees resolved from NODE.md owners by default.

Keep --sync-apply as a deprecated alias of --sync-mode apply for one minor version (warn on use, remove in next minor).

Also add:

--sync-assignee USER
  Optional. Override NODE.md-resolved owners for every issue opened by
  `--sync-mode open-issues`. Matches the semantics of `gardener sync
  --open-issues --assignee USER`. Intended for testing against third-party
  open-source repos where you don't want to ping real domain owners.
  When omitted, issues assign to NODE.md owners (production default).

Why enum over independent flags

--sync-apply and a hypothetical --sync-open-issues can't both run in the same sweep — they produce two overlapping tree-write workflows (one aggregated PR + N draft-node PRs for the same proposals), which would conflict. Making them modes of a single --sync-mode flag makes the mutual exclusion structural instead of a runtime check.

Why --sync-assignee as a separate flag

--sync-mode open-issues is the only mode that takes an assignee (detect/apply don't). Gating it behind open-issues in the parser prevents the user from passing it in the wrong mode.

Implementation sketch

Files to touch (all under `src/products/gardener/`):

  1. `engine/commands/start.ts` — CLI parsing: add `--sync-mode` + `--sync-assignee`; deprecate `--sync-apply` as alias with a warning; update USAGE string.
  2. `engine/daemon/config.ts` — Extend `DaemonConfig`: replace `syncApply: boolean` with `syncMode: 'detect' | 'apply' | 'open-issues'`; add `syncAssignee?: string`. Keep `syncApply` in the raw config parser for backward compat (maps to `syncMode: 'apply'`, logs deprecation warning).
  3. `engine/daemon/loop.ts` — Where `syncApply` is read (line ~169), switch on `syncMode`:
    • `detect` → no extra args
    • `apply` → push `--apply`
    • `open-issues` → push `--open-issues`; if `syncAssignee` set, also push `--assignee `
  4. `skills/first-tree/references/onboarding.md` — Step 6.x: document `gardener start --sync-mode open-issues` as the way to keep the issue-first chain running autonomously.
  5. `skills/gardener/SKILL.md` — same.

Expected change: ~30-50 lines code + ~100-200 lines tests.

Acceptance criteria

  • `first-tree gardener start --sync-mode open-issues --tree-path ... --code-repo ...` starts the daemon and the sync-sweep invokes `gardener sync --open-issues` each interval.
  • `first-tree gardener start --sync-mode apply ...` behaves exactly like the current `--sync-apply`.
  • `first-tree gardener start --sync-apply ...` still works (prints deprecation warning, maps to `--sync-mode apply`).
  • `first-tree gardener start --sync-mode open-issues --sync-assignee alice ...` opens all issues assigned to alice regardless of NODE.md owners.
  • `first-tree gardener start --sync-mode apply --sync-assignee alice ...` rejects with a clear parser error (`--sync-assignee` is only valid with `--sync-mode open-issues`).
  • Existing daemon config-persistence tests still pass (or are updated for the enum shape).
  • Help output explains all three modes clearly.

E2E testing requirement

The implementation PR must include a genuine end-to-end test, not just unit tests on the parser / loop:

  • Start the daemon with `--sync-mode open-issues --sync-assignee `.
  • Wait for one sync-sweep to fire.
  • Verify an issue was opened on the tree repo with:
    • label `first-tree:sync-proposal`
    • assignee matching `--sync-assignee` (or NODE.md owner when omitted)
    • valid `gardener:sync-proposal` marker in the body
  • (Recommended) Let breeze pick it up; verify a `first-tree/draft-node-*` PR is opened on the tree repo resolving the issue.
  • Clean up the test issue + PR at teardown.

This test can extend `tests/gardener/sync-open-issues.test.ts` + `tests/gardener/daemon/*`. A mock of the GitHub + breeze surface is fine as long as it exercises the real daemon loop path (not just the CLI parse layer).

Why this matters

Without this, the autonomous issue-first workflow (the one documented in onboarding.md Step 6 and tested end-to-end in v0.2.14/v0.2.15) requires the user to run `sync --open-issues` manually or via a side cron. That defeats the purpose of having a `gardener start` daemon.

Env / context

/cc @serenakeyitan

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions