Skip to content

feat(gardener/daemon): add --sync-mode enum + --sync-assignee override#342

Open
serenakeyitan wants to merge 1 commit intomainfrom
feat/daemon-sync-mode
Open

feat(gardener/daemon): add --sync-mode enum + --sync-assignee override#342
serenakeyitan wants to merge 1 commit intomainfrom
feat/daemon-sync-mode

Conversation

@serenakeyitan
Copy link
Copy Markdown
Contributor

Closes #341.

Summary

  • Replace daemon boolean --sync-apply with --sync-mode <detect|apply|open-issues>. open-issues lets the daemon periodically run gardener sync --open-issues, closing the autonomous issue-first chain that previously required a side cron.
  • Add --sync-assignee USER override, valid only with --sync-mode open-issues. Forwards to the child sync process's --assignee flag so testing against third-party repos doesn't spam real NODE.md owners.
  • Keep --sync-apply as a deprecated alias (warns + maps to --sync-mode apply; mutex with --sync-mode). Legacy on-disk configs with syncApply: true coerce at load time.

Implementation

4 files, ~370 lines (most of that is tests):

  • engine/daemon/config.tsSyncMode type, coerceSyncMode with legacy fallback, drop syncAssignee outside open-issues mode.
  • engine/daemon/loop.tsbuildSyncSweepArgs switches on mode, forwards --assignee.
  • engine/commands/start.ts — parse --sync-mode / --sync-assignee, validate combinations, emit deprecation warning, update USAGE.
  • tests/gardener/gardener-daemon.test.ts — 15 new unit tests covering config round-trip, legacy coercion, arg builder per mode, CLI parser happy + error paths.

Acceptance criteria (from #341)

All 7 checked off in the commit body. Key ones:

  • --sync-mode open-issues daemon sync-sweep invokes gardener sync --open-issues.
  • --sync-mode apply preserves old --sync-apply behavior.
  • Deprecated --sync-apply still works, prints warning, maps to apply.
  • --sync-assignee forwards to child sync --assignee in open-issues mode only.
  • --sync-assignee in non-open-issues modes → parser error.
  • --sync-apply + --sync-mode together → parser error.
  • Legacy on-disk configs still load correctly.

E2E tests (required by #341)

Six scenarios exercised against the built CLI (not just unit tests):

  1. --sync-mode open-issues --sync-assignee serenakeyitan --dry-run → preview matches; dry-run honored (no config written).
  2. Deprecated --sync-apply --dry-run → warning printed, maps to apply.
  3. --sync-mode garbage → exit=1, clear error naming accepted values.
  4. --sync-mode apply --sync-assignee alice → exit=1, cites mode.
  5. --sync-apply --sync-mode apply → exit=1, "mutually exclusive".
  6. Real daemon boot (no dry-run) with --sync-mode open-issues --sync-assignee serenakeyitan → on-disk config.json has syncMode: "open-issues" and syncAssignee: "serenakeyitan"; daemon stops cleanly.

Validation

  • pnpm typecheck clean.
  • pnpm build clean.
  • pnpm vitest run tests/gardener/gardener-daemon.test.ts — 47/47 tests pass (up from 32).
  • Full unit suite green: 1215 passed, 51 skipped (skips are pre-existing agent-e2e).

Follow-ups (not in this PR)

  • Update onboarding.md / SKILL.md docs to document --sync-mode open-issues as the autonomous-chain setup. Will land in a separate docs PR once this lands to keep the review surface tight.
  • When --sync-apply is removed in a future minor, also drop the syncApply: boolean back-compat branch in coerceSyncMode.

Closes #341.

## What

`gardener start` previously exposed only `--sync-apply` (boolean),
which mapped to `gardener sync --apply` on each sync-sweep. There was
no way to configure the daemon to run `gardener sync --open-issues`,
so the autonomous issue-first chain (source PR → tree issue → breeze
→ draft-node PR) required a side cron to call `sync --open-issues`
periodically.

This PR replaces the boolean with a three-value enum and adds an
optional assignee override:

  --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; breeze + draft-node handle each.

  --sync-assignee <user>
    Only valid with `--sync-mode open-issues`. Overrides NODE.md-
    resolved owners so every opened issue is assigned to this user.
    Intended for testing against third-party repos where you don't
    want to ping real domain owners.

## Back-compat

`--sync-apply` is retained as a deprecated alias:

  - Emits a deprecation warning on use.
  - Maps to `--sync-mode apply`.
  - Mutually exclusive with `--sync-mode` (parser rejects both).
  - Legacy on-disk configs that still have `syncApply: true` and no
    `syncMode` coerce to `syncMode: "apply"` at load time.

Will be removed in a future minor.

## Implementation

- `engine/daemon/config.ts`: add `SyncMode` type, replace
  `GardenerDaemonConfig.syncApply` with `syncMode` + optional
  `syncAssignee`. `coerceSyncMode` handles the legacy-config path.
  `buildDaemonConfig` accepts either `syncMode` or the legacy
  `syncApply`; `syncMode` wins. Drops `syncAssignee` when mode is not
  open-issues so CLI-level validation isn't bypassed via the builder.
- `engine/daemon/loop.ts`: `buildSyncSweepArgs` switches on
  `syncMode`; forwards `--assignee <user>` to the child sync process
  only when configured.
- `engine/commands/start.ts`: extend parser with `--sync-mode`,
  `--sync-assignee`, deprecated `--sync-apply` handling. Validate
  mode/assignee/deprecation combinations with clear error messages.
  Update USAGE to explain all three modes.

## Acceptance criteria (from #341)

- [x] `gardener start --sync-mode open-issues ...` starts the daemon
  and sync-sweep invokes `gardener sync --open-issues`.
- [x] `gardener start --sync-mode apply ...` behaves like the old
  `--sync-apply`.
- [x] `gardener start --sync-apply ...` still works, prints
  deprecation warning, maps to `--sync-mode apply`.
- [x] `gardener start --sync-mode open-issues --sync-assignee alice
  ...` forwards `--assignee alice` to the child sync process.
- [x] `gardener start --sync-mode apply --sync-assignee alice ...`
  rejects with a clear parser error.
- [x] `gardener start --sync-apply --sync-mode apply ...` rejects
  with "mutually exclusive" error.
- [x] Existing daemon config-persistence tests still pass. Added new
  tests for the enum shape + legacy coercion.
- [x] Help output explains all three modes clearly.

## Tests

15 new unit tests in `tests/gardener/gardener-daemon.test.ts`:
- config round-trip for detect / apply / open-issues modes
- syncAssignee persisted in open-issues, dropped in other modes
- legacy `syncApply: true` on-disk config coerces to `syncMode:
  "apply"`
- buildSyncSweepArgs for each mode, with/without assignee
- CLI parser: accept, reject invalid mode, reject assignee outside
  open-issues, reject sync-apply + sync-mode mutex, deprecation
  warning on sync-apply

All 47 tests in `gardener-daemon.test.ts` pass; the file grew from
32 to 47 tests.

## E2E

Six scenarios exercised end-to-end against the built CLI (not just
unit tests), required by #341:

1. `--sync-mode open-issues --sync-assignee serenakeyitan --dry-run`
   → preview shows `sync-mode: open-issues`, `sync-assignee:
   serenakeyitan`; no config.json written (dry-run honored).
2. `--sync-apply --dry-run` → deprecation warning printed, config
   preview shows `sync-mode: apply`.
3. `--sync-mode garbage --dry-run` → exit=1, clear error naming
   accepted values.
4. `--sync-mode apply --sync-assignee alice --dry-run` → exit=1,
   clear error citing mode.
5. `--sync-apply --sync-mode apply --dry-run` → exit=1, "mutually
   exclusive".
6. Real daemon boot (no dry-run) with `--sync-mode open-issues
   --sync-assignee serenakeyitan` → config.json on disk has
   `syncMode: "open-issues"` and `syncAssignee: "serenakeyitan"`;
   daemon stops cleanly.

## Env

- Built against first-tree main @ a03a852 (v0.3.2 release).
- Typecheck clean; build clean; full unit suite green
  (1215 passed, 51 skipped).
@serenakeyitan
Copy link
Copy Markdown
Contributor Author

@bingran-you friendly ping — could you take a look when you have a moment? This closes #341 (the daemon --sync-mode open-issues gap). Happy to address any feedback quickly.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

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

1 participant