Skip to content

refactor(daemon): extract SkillAutoReleaseSettings — AceClawConfig decomposition batch 1#506

Merged
xinhuagu merged 1 commit into
mainfrom
refactor/aceclaw-config-batch1
May 23, 2026
Merged

refactor(daemon): extract SkillAutoReleaseSettings — AceClawConfig decomposition batch 1#506
xinhuagu merged 1 commit into
mainfrom
refactor/aceclaw-config-batch1

Conversation

@xinhuagu

@xinhuagu xinhuagu commented May 23, 2026

Copy link
Copy Markdown
Owner

Summary

First pilot of the AceClawConfig decomposition. The class had grown to
152 instance fields + 85 getters — a database-schema-masquerading-as-a-class
that's been the central catch-all for every daemon-side knob.

This PR carves out one thematic cluster (skill auto-release, 12 fields)
into its own record. Same pattern that pulled aceclaw-learning out of
daemon: extract a cohesive slice first, validate the pattern works, then
batch the rest.

Before After
AceClawConfig.java 2,038 LoC 1,879 LoC (-8%)
AceClawConfig instance fields 152 141 (-12 cluster + 1 new)
AceClawConfig getters 85 74 (-12 + 1)

Pilot target: SkillAutoRelease (12 fields)

Chosen first because:

  • biggest single cohesive cluster (~130 LoC of env-var loading alone)
  • consumer (AutoReleaseController in aceclaw-learning) already has
    its own Config record — reuse it as the tuning payload instead of
    inventing a new one
  • single call site in AceClawDaemon (11 inline scalars to a constructor)

Shape

public record SkillAutoReleaseSettings(
    boolean enabled,
    AutoReleaseController.Config tuning
) { }

enabled is the feature gate (was skillAutoReleaseEnabled); tuning
groups the 11 canary/rollback scalars the controller already accepts as a
record. A Builder lives on the record for incremental population during
file/env load — kept package-private; only AceClawConfig uses it.

What moved

Was in AceClawConfig Now
13 DEFAULT_SKILL_AUTO_RELEASE_* constants SkillAutoReleaseSettings.defaults()
12 instance fields 1 builder field
12 constructor-default assignments none (builder seeds)
12 individual getters 1 skillAutoRelease() getter
~130 LoC of env-var try/catch builder.xxx(parsed) per env var
~55 LoC of if (fileConfig.x != null) single builder chain

Daemon call site

// Before (11 inline scalars passed by hand)
if (config.skillAutoReleaseEnabled()) {
    autoReleaseController = new AutoReleaseController(
        new AutoReleaseController.Config(
            config.skillAutoReleaseMinCandidateScore(),
            config.skillAutoReleaseMinEvidence(),
            // ... 9 more
        ),
        validationGateForAuto);
}

// After
var skillAutoRelease = config.skillAutoRelease();
if (skillAutoRelease.enabled()) {
    autoReleaseController = new AutoReleaseController(
        skillAutoRelease.tuning(),
        validationGateForAuto);
}

Bonus: env-loading helpers

Pulled three small helpers — applyEnvBoolean, applyEnvInt,
applyEnvDouble — out of the env-var-loading boilerplate (was inlined
~50× across load()). Each handles null/blank + parse-failure-with-warning
centrally. Future thematic extractions will reuse them.

Backward compat

  • Config file JSON shape unchanged — same flat top-level keys
    (skillAutoReleaseEnabled, skillAutoReleaseMinCandidateScore, …)
  • All 14 env var names preserved (including the legacy
    ACECLAW_SKILL_AUTO_RELEASE_ACTIVE_MAX_FAILURE_RATE alias mapping onto
    rollbackMaxFailureRate)
  • AceClawConfigTest passes unchanged (17/0)

Test plan

  • :aceclaw-daemon:compileJava — clean
  • :aceclaw-daemon:compileTestJava — clean
  • AceClawConfigTest — 17/0 (covers config file loading, env var
    overrides, persistence, security.denySensitivePaths toggle)
  • Diff stat: +254 / -285 = -31 net (across 3 files)
  • Renamed/moved code: 0 (this is pure extraction; no logic changes)

Follow-up batches (out of scope for this PR)

Plan: 2-3 more PRs lift remaining cohesive clusters:

Cluster Fields Notes
SkillDraftValidation 5 similar shape
AdaptiveContinuation 5 very cohesive
Candidates (injection + promotion) 7 two sub-clusters could split further
Retry 4 already has a RetryConfigFormat JSON companion
Agent + Plan budgets 6 (Watchdog cluster)
AntiPatternGate 2 smallest, last

Each follows this PR's pattern: small record (+ Builder when needed),
single call site in AceClawDaemon migrated, individual getters removed.

🤖 Generated with Claude Code

Summary by CodeRabbit

Release Notes

Refactor

  • Consolidated skill auto-release configuration settings into a unified object structure for improved configuration management and maintainability.

Review Change Stack

…onfig sub-record

AceClawConfig had grown to 152 instance fields + 85 getters — by far the
widest single class in the repo, a database-schema-masquerading-as-a-class
that's been the central catch-all for every daemon-side knob. This is the
first pilot of the decomposition: lift one thematic cluster into its own
record, leave the rest alone for now.

Pilot target: the 12 skill-auto-release scalars (`skillAutoRelease*`).
Chosen first because:

- biggest single cohesive cluster (12 fields / ~130 LoC of env var loading
  alone)
- consumer (AutoReleaseController in aceclaw-learning) already has its own
  Config record — reuse it as the tuning payload instead of inventing a new
  one
- single call site in AceClawDaemon: 11 inline scalars going to a
  constructor — perfect surface for "pass the whole record" simplification

## Shape

```
SkillAutoReleaseSettings(boolean enabled, AutoReleaseController.Config tuning)
```

`enabled` is the feature gate (was `skillAutoReleaseEnabled`); `tuning`
groups the 11 canary / rollback scalars the controller already accepts as
a record. The settings type has a `Builder` for incremental population
during file/env load (kept package-private; only AceClawConfig uses it).

## What moved

| Was in AceClawConfig | Now |
|---|---|
| 13 `DEFAULT_SKILL_AUTO_RELEASE_*` constants | `SkillAutoReleaseSettings.defaults()` |
| 12 instance fields (`skillAutoRelease*`) | 1 builder field |
| 12 constructor-default assignments | none (builder seeds defaults) |
| 12 individual getters | 1 getter (`skillAutoRelease()`) |
| ~130 LoC of env-var try/catch boilerplate | builder.xxx(parsedValue) per env var |
| ~55 LoC of `if (fileConfig.x != null) { ... }` blocks | single builder chain |

## Net diff

| File | LoC change |
|---|---|
| AceClawConfig.java | **2038 → 1879 (-159, -8%)** |
| AceClawDaemon.java | -10 |
| SkillAutoReleaseSettings.java (new) | +139 |

The 139 LoC of the new record is mostly per-field setters on the builder
(needed for the null-safe / range-checked merge) and the defaults factory.
Net file-system delta is roughly zero, but the wide-class surface of
AceClawConfig shrinks by 12 fields + 12 getters — the real win.

## Bonus: applyEnv{Boolean,Int,Double} helpers

Pulled three small helpers out of the env-var-loading boilerplate (was
inlined ~50x across the load() method). Used by the new skillAutoRelease
loaders; future thematic extractions will reuse them. Each handles
null/blank + parse-failure-with-warning centrally. Same observable
behaviour as the inlined version.

## Backward compat

- Config file JSON shape unchanged. Same flat top-level keys
  (`skillAutoReleaseEnabled`, `skillAutoReleaseMinCandidateScore`, …).
  Nesting under a `skillAutoRelease` object is a future option; not in
  this PR to keep the diff narrow.
- All 14 env var names preserved (including the legacy
  `ACECLAW_SKILL_AUTO_RELEASE_ACTIVE_MAX_FAILURE_RATE` alias mapping onto
  `rollbackMaxFailureRate`).
- AceClawConfigTest passes unchanged (17/0).

## Test plan

- [x] :aceclaw-daemon:compileJava
- [x] :aceclaw-daemon:compileTestJava (-Pno-dashboard to skip unrelated
  npm build)
- [x] AceClawConfigTest (17 tests, 0 failures) — covers config file
  loading, env var overrides, persistence, and the security.denySensitivePaths
  toggle added previously
- [x] No call site changes outside the single AceClawDaemon
  AutoReleaseController construction

## Pattern for follow-up batches

Plan was 2-3 PRs over a long weekend. Next thematic clusters teed up:
SkillDraftValidation (5 fields), AdaptiveContinuation (5), Candidate
injection/promotion (7), Retry (4), AgentBudgets + PlanBudgets (6),
AntiPatternGate (2). Each follows the same pattern: small record (+ Builder
when there are >5 fields), single call site in AceClawDaemon migrated to
pass the record, individual getters removed.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@qodo-code-review

Copy link
Copy Markdown

Qodo reviews are paused for this user.

Troubleshooting steps vary by plan Learn more →

On a Teams plan?
Reviews resume once this user has a paid seat and their Git account is linked in Qodo.
Link Git account →

Using GitHub Enterprise Server, GitLab Self-Managed, or Bitbucket Data Center?
These require an Enterprise plan - Contact us
Contact us →

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Your free trial has ended. If you'd like to continue receiving code reviews, you can add a payment method here.

@coderabbitai

coderabbitai Bot commented May 23, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

This PR consolidates 13 scattered skill auto-release configuration fields and public getters from AceClawConfig into a single SkillAutoReleaseSettings record. A new builder pattern with centralized env-var parsing helpers updates the config loading pipeline, and AceClawDaemon is refactored to consume the consolidated object.

Changes

Skill Auto-Release Settings Consolidation

Layer / File(s) Summary
SkillAutoReleaseSettings record and builder
aceclaw-daemon/src/main/java/dev/aceclaw/daemon/SkillAutoReleaseSettings.java
New SkillAutoReleaseSettings record encapsulates the feature-gate flag and AutoReleaseController.Config tuning. The defaults() factory sets fixed thresholds and converts health lookback to a 7-day Duration. The internal Builder copies from an existing record, applies nullable overrides with clamping (rates to [0.0, 1.0]) and minimums (counts), handles legacy config alias mapping for activeMaxFailureRate → rollback failure rate, and converts healthLookbackHours to a minimum-1-hour Duration during build.
AceClawConfig builder field and parsing refactoring
aceclaw-daemon/src/main/java/dev/aceclaw/daemon/AceClawConfig.java
Replaces 13 scalar skill auto-release fields with a single skillAutoReleaseBuilder. Removed default constants now live in SkillAutoReleaseSettings.defaults(). Environment-variable and file-override loading logic funnels through private parsing helpers (applyEnvBoolean, applyEnvInt, applyEnvDouble) that route values into the builder. New public skillAutoRelease() accessor returns builder.build().
AceClawDaemon auto-release wiring update
aceclaw-daemon/src/main/java/dev/aceclaw/daemon/AceClawDaemon.java
Self-improvement engine boot wiring now uses config.skillAutoRelease() object: feature-gate check uses skillAutoRelease.enabled(), and AutoReleaseController construction passes skillAutoRelease.tuning() instead of assembling a config inline from individual parameter getters.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • xinhuagu/AceClaw#296: Both PRs modify the same auto-release configuration path; #296 introduces canaryDwellHours threading through the config stack, while this PR refactors those fields into the SkillAutoReleaseSettings record and updates consumption.
  • xinhuagu/AceClaw#86: This PR refactors the existing skill auto-release configuration introduced in #86, consolidating scalar fields into a record-based design.

Suggested labels

breaking-change, needs-tests

🚥 Pre-merge checks | ✅ 7 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 28.57% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (7 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change: extracting skill auto-release configuration into a new SkillAutoReleaseSettings record as part of AceClawConfig decomposition.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Block Major Correctness And Security Risks ✅ Passed No major/high-risk issues. Refactoring properly encapsulates config with immutable record, validates data ranges, handles errors gracefully, and maintains backward compatibility.
Require Test Coverage For New Logic ✅ Passed Pure refactor consolidating skill auto-release config fields into a record. Behavior unchanged—JSON shape, env vars, defaults preserved. AceClawConfigTest (17 tests) passes.
No Api Breaking Changes Without Version Bump ✅ Passed PR bumped version to 0.4.4-SNAPSHOT in gradle.properties, satisfying "version number OR migration note" requirement despite removing 12 public methods from AceClawConfig.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch refactor/aceclaw-config-batch1

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 4e8f93f0b5

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +588 to +589
applyEnvInt("ACECLAW_SKILL_AUTO_RELEASE_MIN_EVIDENCE",
v -> config.skillAutoReleaseBuilder.minEvidenceCount(Math.max(1, v)));

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Preserve zero min evidence from env override

When ACECLAW_SKILL_AUTO_RELEASE_MIN_EVIDENCE is set to 0, this path now coerces it to 1 via Math.max(1, v), whereas the previous implementation accepted 0 (it used Math.max(0, parsed)). That changes runtime behavior for env-based deployments by making auto-release stricter than configured, and it contradicts the refactor goal of preserving existing semantics.

Useful? React with 👍 / 👎.

Comment on lines +96 to +100
Builder canaryMaxFailureRate(Double v) { if (v != null && v >= 0) this.canaryMaxFailureRate = clampRate(v); return this; }
Builder canaryMaxTimeoutRate(Double v) { if (v != null && v >= 0) this.canaryMaxTimeoutRate = clampRate(v); return this; }
Builder canaryMaxPermissionRate(Double v) { if (v != null && v >= 0) this.canaryMaxPermissionRate = clampRate(v); return this; }
Builder rollbackMaxFailureRate(Double v) { if (v != null && v >= 0) this.rollbackMaxFailureRate = clampRate(v); return this; }
Builder rollbackMaxTimeoutRate(Double v) { if (v != null && v >= 0) this.rollbackMaxTimeoutRate = clampRate(v); return this; }

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Keep negative rate env values clamped instead of ignored

These setters now ignore negative numeric values because of the v >= 0 guard, but the pre-refactor env parsing path fed parsed doubles directly through clampRate, so negative values were normalized to 0.0. With this change, an env override like -1 no longer tightens a threshold to zero and instead leaves the previous value in place, which is a silent behavior change for existing env-driven configs.

Useful? React with 👍 / 👎.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick comments (2)
aceclaw-daemon/src/main/java/dev/aceclaw/daemon/SkillAutoReleaseSettings.java (1)

27-27: ⚡ Quick win

Enforce non-null tuning at record boundary.

Because this record is public, allowing tuning to be null pushes failure to later dereferences. Fail fast in the record constructor.

♻️ Proposed fix
 public record SkillAutoReleaseSettings(boolean enabled, AutoReleaseController.Config tuning) {
+    public SkillAutoReleaseSettings {
+        tuning = java.util.Objects.requireNonNull(tuning, "tuning");
+    }

As per coding guidelines: "Use Objects.requireNonNull(param, "param") on method parameters used in .equals() or passed to downstream calls".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@aceclaw-daemon/src/main/java/dev/aceclaw/daemon/SkillAutoReleaseSettings.java`
at line 27, The public record SkillAutoReleaseSettings currently allows a null
tuning which defers failures; add a compact canonical constructor to the record
that calls Objects.requireNonNull(tuning, "tuning") to fail fast and enforce
non-null; update imports to include java.util.Objects if missing and ensure the
constructor is declared inside the SkillAutoReleaseSettings record definition so
all instantiations validate tuning at creation.
aceclaw-daemon/src/main/java/dev/aceclaw/daemon/AceClawConfig.java (1)

1854-1878: ⚡ Quick win

Null-guard helper parameters before downstream use.

These helpers pass name/sink directly to downstream calls; add explicit requireNonNull guards to keep failures deterministic and aligned with project conventions.

♻️ Proposed fix
 private static void applyEnvBoolean(String name, java.util.function.Consumer<Boolean> sink) {
+    java.util.Objects.requireNonNull(name, "name");
+    java.util.Objects.requireNonNull(sink, "sink");
     var raw = System.getenv(name);
     if (raw == null || raw.isBlank()) return;
     sink.accept(Boolean.parseBoolean(raw));
 }
 
 private static void applyEnvInt(String name, java.util.function.IntConsumer sink) {
+    java.util.Objects.requireNonNull(name, "name");
+    java.util.Objects.requireNonNull(sink, "sink");
     var raw = System.getenv(name);
     if (raw == null || raw.isBlank()) return;
     try {
         sink.accept(Integer.parseInt(raw));
@@
 private static void applyEnvDouble(String name, java.util.function.DoubleConsumer sink) {
+    java.util.Objects.requireNonNull(name, "name");
+    java.util.Objects.requireNonNull(sink, "sink");
     var raw = System.getenv(name);
     if (raw == null || raw.isBlank()) return;
     try {
         sink.accept(Double.parseDouble(raw));

As per coding guidelines: "Use Objects.requireNonNull(param, "param") on method parameters used in .equals() or passed to downstream calls".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@aceclaw-daemon/src/main/java/dev/aceclaw/daemon/AceClawConfig.java` around
lines 1854 - 1878, Add explicit null checks at the start of the helper methods
to match project conventions: in applyEnvBoolean, applyEnvInt, and
applyEnvDouble call Objects.requireNonNull(name, "name") and
Objects.requireNonNull(sink, "sink") before any downstream use (e.g., before
System.getenv and before invoking sink.accept) so parameter nulls fail fast and
deterministically; keep the existing parsing and warning behavior unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@aceclaw-daemon/src/main/java/dev/aceclaw/daemon/AceClawConfig.java`:
- Around line 1854-1878: Add explicit null checks at the start of the helper
methods to match project conventions: in applyEnvBoolean, applyEnvInt, and
applyEnvDouble call Objects.requireNonNull(name, "name") and
Objects.requireNonNull(sink, "sink") before any downstream use (e.g., before
System.getenv and before invoking sink.accept) so parameter nulls fail fast and
deterministically; keep the existing parsing and warning behavior unchanged.

In
`@aceclaw-daemon/src/main/java/dev/aceclaw/daemon/SkillAutoReleaseSettings.java`:
- Line 27: The public record SkillAutoReleaseSettings currently allows a null
tuning which defers failures; add a compact canonical constructor to the record
that calls Objects.requireNonNull(tuning, "tuning") to fail fast and enforce
non-null; update imports to include java.util.Objects if missing and ensure the
constructor is declared inside the SkillAutoReleaseSettings record definition so
all instantiations validate tuning at creation.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 6d2b4e71-a083-4c6f-bbb7-ffc92df334d3

📥 Commits

Reviewing files that changed from the base of the PR and between 198bb49 and 4e8f93f.

📒 Files selected for processing (3)
  • aceclaw-daemon/src/main/java/dev/aceclaw/daemon/AceClawConfig.java
  • aceclaw-daemon/src/main/java/dev/aceclaw/daemon/AceClawDaemon.java
  • aceclaw-daemon/src/main/java/dev/aceclaw/daemon/SkillAutoReleaseSettings.java

@xinhuagu xinhuagu merged commit fb7a7e3 into main May 23, 2026
11 checks passed
@xinhuagu xinhuagu deleted the refactor/aceclaw-config-batch1 branch May 23, 2026 22:18
xinhuagu added a commit that referenced this pull request May 24, 2026
Two findings, both real:

## Bug — AntiPatternGateSettings.maxFalsePositiveRate negative-value
clamp regression

Pre-decomp env-var path called `clampRate(parsed)` unconditionally, so a
negative env value (e.g. `ACECLAW_ANTI_PATTERN_GATE_MAX_FALSE_POSITIVE_RATE=-0.1`)
became 0.0 rather than being silently ignored. The `v >= 0` guard in the
new Builder.maxFalsePositiveRate diverged from that — Codex P2 + CodeRabbit
minor both flagged it.

Fix: any non-null input is now clamped to [0, 1] before being stored,
matching the pre-decomp env-var behaviour. File-merge behaviour also
unified to clamp (was skip-then-clamp before; now consistent).

The same regression pattern (rate fields gated by `v >= 0` instead of
clamping unconditionally) likely exists in SkillAutoReleaseSettings'
7 rate fields, already merged in #506. Deferred to a follow-up audit PR
so this PR stays narrow.

## Nitpick — Builder seed null-guards (3 records)

Per project CLAUDE.md "Use Objects.requireNonNull(param, "param") on
method parameters used in .equals() or passed to downstream calls":

- AntiPatternGateSettings.Builder(seed)
- CandidatePromotionSettings.Builder(seed)
- CandidateInjectionSettings.Builder(seed)

All three dereference `seed` immediately. Adding the guard so a null
gets a clear "seed" NPE message instead of a confusing one at the first
accessor call. SkillAutoReleaseSettings / AdaptiveContinuationSettings /
SkillDraftValidationSettings (already-merged Builders) have the same
shape — same follow-up PR.

## Test plan

- [x] AceClawConfigTest 17/0 (covers env-var loading)
- [x] Build clean

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant