Skip to content

fix(state): persist model assignments across sync runs#265

Merged
Alan-TheGentleman merged 4 commits intoGentleman-Programming:mainfrom
christorres-hu:fix/persist-model-assignments
Apr 12, 2026
Merged

fix(state): persist model assignments across sync runs#265
Alan-TheGentleman merged 4 commits intoGentleman-Programming:mainfrom
christorres-hu:fix/persist-model-assignments

Conversation

@christorres-hu
Copy link
Copy Markdown
Contributor

@christorres-hu christorres-hu commented Apr 9, 2026

Closes #264

Summary

  • Fixes model assignments being silently reset to "balanced" preset on every sync run
  • CLI sync and TUI sync now load persisted assignments from state.json when no flags/overrides are provided
  • Read-merge-write pattern ensures InstalledAgents and other state fields are preserved
  • Added 7 integration tests covering CLI load path, TUI round-trip, backward compat, and multi-sync cycle

Design note

claudeAliasesToStrings and modelAssignmentsToState are intentionally duplicated as unexported helpers in app and cli — avoids coupling state to model for two 8-line functions.

Test plan

  • TestRunSyncLoadsPersistedModelAssignments — CLI sync loads from state.json
  • TestRunSyncDoesNotOverridePersistedAssignmentsOnSecondSync — assignments survive sync1 → sync2
  • TestRunSyncWithNoPersistedAssignmentsDoesNotPanic — backward compat with old state
  • TestLoadPersistedAssignmentsPopulatesEmptySelection — TUI load path
  • TestLoadPersistedAssignmentsDoesNotOverrideExisting — TUI overrides not clobbered
  • TestPersistAssignmentsPreservesInstalledAgents — read-merge-write safety
  • TestPersistAssignmentsNoOpWhenEmpty — no spurious writes
  • Full suite: go test ./... — 37 packages, 0 failures

Model assignments selected via the TUI or install were only held in
memory during the session. Every subsequent `gentle-ai sync` (TUI or
CLI) rebuilt the Selection with empty assignment maps, causing
sdd.Inject to fall back to the hardcoded "balanced" preset and
silently overwrite the user's choices.

Root cause: InstallState only serialised `installed_agents` — the
ClaudeModelAssignments and ModelAssignments fields were never written
to ~/.gentle-ai/state.json.

This commit:
- Adds ClaudeModelAssignments and ModelAssignments fields to
  InstallState (backward-compatible via omitempty)
- Changes state.Write to accept the full InstallState struct
- Persists assignments during both install and TUI sync flows
- Loads persisted assignments in RunSync (CLI) and tuiSync (TUI)
  when no explicit overrides are provided
- Adds round-trip and backward-compat tests for the new fields

Fixes: model assignments reset to "balanced" on every sync
…c runs

Covers CLI sync load path, TUI load/persist round-trip, read-merge-write
safety, backward compat with pre-feature state files, and multi-sync cycle.
Copy link
Copy Markdown
Contributor

@Alan-TheGentleman Alan-TheGentleman left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review: fix(state): persist model assignments across sync runs

Verdict: APPROVE

Clean, well-tested fix. The read-merge-write pattern is correct, backward compatibility is covered by the omitempty tags and the legacy-state test, and the 7 new tests cover the important scenarios (load, no-clobber, merge, no-op, multi-sync cycle, backward compat).

A few non-blocking observations:

  1. Duplicated helpersclaudeAliasesToStrings / modelAssignmentsToState appear identically in both app and cli. Acknowledged in the PR description as intentional to avoid coupling. If a third caller appears, consider extracting to a shared internal package.

  2. Inline vs. extracted load logic — The TUI path extracts loadPersistedAssignments as a clean helper, but sync.go has the same logic inline. Minor consistency nit.

  3. TUI persists after sync, CLI does nottuiSync calls persistAssignments after execution; RunSync does not. Currently harmless since nothing mutates assignments between load and execute, but the asymmetry is worth a comment for future maintainers.

  4. ModTime-based no-op testTestPersistAssignmentsNoOpWhenEmpty compares file timestamps. On coarse-grained filesystems this could theoretically be fragile. Content comparison would be more robust, but unlikely to flake in practice.

Overall: solid work, good test coverage, clear PR description. Ship it.

@Alan-TheGentleman Alan-TheGentleman added the type:bug Bug fix label Apr 12, 2026
…test

engram setup claude-code writes to settings.json with non-deterministic
Go map iteration order. First vs second run produces byte-different but
semantically identical JSON, causing md5sum comparison to false-fail.

Add json_files_equal helper using python3 for key-order-agnostic
comparison and use it in test_idempotent_full_claude.
@Alan-TheGentleman Alan-TheGentleman merged commit 62c486e into Gentleman-Programming:main Apr 12, 2026
7 of 8 checks passed
Alan-TheGentleman added a commit to IrrealV/gentle-ai that referenced this pull request Apr 13, 2026
…g#265 state.Write API change

- Adapt uninstall service to use state.InstallState instead of []string
- Update app.go dispatch to match RunUninstall return signature
- Replace incompatible uninstall tests with PR-compatible version
Alan-TheGentleman added a commit that referenced this pull request Apr 13, 2026
* fix(uninstall): harden managed cleanup and atomic writes

* fix(uninstall): address second audit findings

* docs(usage): document granular uninstall flow

* feat(tui): add uninstall mode selector with workspace asset warnings

- Add ScreenUninstallMode as entry point for uninstall flow
- Three modes: Partial (granular selection), Full (all agents/components), Full & Remove (includes binary deletion)
- Workspace asset warnings for .engram/, workflows/, and skills/
- Full & Remove mode uses os.Remove() to delete gentle-ai binary after successful uninstall
- Updated tests to reflect new navigation flow through mode selector
- Closes part of #102

* feat(uninstall): report manual cleanup required items

- Extend uninstall result with ManualActions
- Report directories left non-empty after managed cleanup
- Show manual cleanup section in CLI and TUI uninstall results
- Add tests for service, CLI report, and TUI result rendering

* fix(uninstall): resolve Copilot review feedback on TUI and service logic

* feat(uninstall): add manual CLI command cleanup instruction on full uninstall

* feat(uninstall): add 'Full Uninstall + Clean Install' mode

Adds a 4th uninstall mode that removes all managed configuration and
immediately re-syncs all managed assets from scratch. This provides
a factory-reset workflow to fix broken configurations without manually
re-running install/sync commands.

The sync runs after uninstall completes. Sync errors are non-fatal and
displayed separately in the result screen so the user always sees the
uninstall status.

* fix(uninstall): clean SDD profiles during removal

* fix(uninstall): wire TUI uninstall path and onboard cleanup

Ensure managed uninstall is reachable from the Welcome menu and correctly wired to the backend callback, and include sdd-onboard in SDD cleanup lists to avoid leftover managed artifacts.

* fix(uninstall): resolve review regressions in full-remove and atomic writes

Restore WriteFileAtomic permission-relaxation compatibility, prevent Homebrew full-remove from deleting managed binaries, and tighten uninstall cleanup safety checks so reviewer-reported edge cases are handled explicitly.

* feat(uninstall): add profile sub-selection inside uninstall flow

Add a conditional profile-selection step so users can choose which OpenCode SDD profiles are removed, while preserving the old flow when no profiles apply.

* feat(uninstall): add Engram project-vs-global cleanup scope

* fix: resolve rebase conflicts with main after PR #265 state.Write API change

- Adapt uninstall service to use state.InstallState instead of []string
- Update app.go dispatch to match RunUninstall return signature
- Replace incompatible uninstall tests with PR-compatible version

---------

Co-authored-by: Alan Buscaglia <gentlemanprogramming@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

type:bug Bug fix

Projects

None yet

Development

Successfully merging this pull request may close these issues.

bug(SDD profiles) new SDD profile replace the SDD default agent

2 participants