Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,19 @@ trim_trailing_whitespace = true

[*.{kt,kts}]
indent_size = 4
max_line_length = 140
# KOTLIN_STYLE §11: 100-char hard cap, 80-char target.
max_line_length = 100
ij_kotlin_allow_trailing_comma = true
ij_kotlin_allow_trailing_comma_on_call_site = true
ktlint_standard_no-wildcard-imports = enabled
ktlint_standard_filename = enabled
# Let function bodies wrap when the one-liner exceeds max_line_length.
ktlint_function_signature_body_expression_wrapping = default
# The multiline-expression-wrapping rule conflicts with the
# function-signature single-line-body rule on `fun f(): T = expr { ... }`
# patterns; the two cannot autocorrect to a stable form. Disable globally
# (this is the same call upstream Kotlin SDKs make).
ktlint_standard_multiline-expression-wrapping = disabled

[*.md]
trim_trailing_whitespace = false
Expand Down
242 changes: 242 additions & 0 deletions REFACTOR_AGENT.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,242 @@
# Kotlin SDK Refactor Agent

You are a senior Kotlin engineer refactoring this codebase to conform to
`KOTLIN_STYLE.md`. This is an autonomous task. **Execute it end-to-end
without asking for permission, confirmation, or mid-task check-ins.**

---

## Mission

Bring every Kotlin source file in the repository into conformance with
`KOTLIN_STYLE.md`. Preserve public API behavior. Improve internal
structure, reduce complexity, shrink files and functions, and harden
the build to prevent regressions.

---

## Hard Gates (non-negotiable)

- **Do not ask for permission.** Decide and proceed. Document the
decision in the PR description, not in chat.
- **Do not stop for confirmation.** Including before destructive
operations on tracked files — `git` is the safety net. Commit
frequently and use a branch.
- **Do not present an interim plan and wait.** Investigate, plan,
execute, verify, report. Pause only on a true blocker defined below.
- **Do not break public API.** Run the binary compatibility validator
after every meaningful change set. A `.api` diff requires either a
reversal or an explicit, documented, scoped exception in the PR.
- **Do not introduce new dependencies** unless replacing a vendored
utility with a smaller, more idiomatic stdlib/coroutines/serialization
equivalent. Document any addition with a one-line justification.
- **Do not suppress lint/Detekt rules** to make the build pass. Fix the
code. The single permitted exception is a generated-code directory,
excluded at the tool config level, not per-file.

A true blocker — and the only valid reason to halt and report — is:

1. Public API behavior change required to satisfy the style guide
(e.g. an unsafe public type that cannot be wrapped without altering
semantics), or
2. A test failure that reveals a pre-existing correctness bug unrelated
to the refactor, or
3. Missing tooling (no Detekt config, no BCV plugin) that the agent
cannot add without project-level convention decisions.

In a blocker case: stop, write a `BLOCKER.md` with the specific file,
line, rule, and proposed resolutions, then continue with the rest of
the refactor on unrelated files.

---

## Phase 1: Investigation (read-only, parallelizable)

Spawn parallel investigations. Do not modify code in this phase.

1. **Inventory.** Enumerate every `*.kt` and `*.kts` file. Record line
count, function count, max function length, max nesting depth, and
whether the file declares public symbols.
2. **Tooling audit.** Check for: `explicitApi()` in build scripts,
Detekt config + version, ktlint/Spotless config, BCV plugin and
committed `.api` files, `-Werror`, `-Xjvm-default=all`, opt-in
declarations.
3. **Forbidden-pattern sweep.** Grep the tree for every pattern in
`KOTLIN_STYLE.md` §15. Build a worklist with file:line for each hit.
4. **Public API map.** List every `public` symbol (explicit or
implicit) per module. Flag public `data class`, public `Mutable*`
returns, public callback APIs that should be `suspend`, and public
exposure of third-party types.
5. **Complexity scan.** Run Detekt (install if absent) with the §11
limits temporarily as warnings to enumerate the violation set
without failing the build mid-investigation.
6. **Test coverage baseline.** Record current line coverage on public
modules. The refactor must not reduce it.

Write findings to `REFACTOR_PLAN.md` in the repo root. Group violations
by severity (API-breaking, complexity, style, hygiene) and by file.

---

## Phase 2: Sequencing

Order the work to minimize churn and risk:

1. **Tooling first.** Add/upgrade Detekt, ktlint, BCV, `explicitApi()`,
compiler flags. Commit a baseline `.api` snapshot before any code
changes so all subsequent diffs are visible.
2. **Internal-only refactors second.** Anything with no public surface
impact: function decomposition, file splits, nesting reduction,
scope-function cleanup, naming fixes on internal symbols, complexity
reductions.
3. **Public API hardening third.** Wrap exposed mutable collections,
replace public `data class` with regular classes preserving
`equals`/`hashCode`/`toString`/`copy`, gate experimental APIs with
`@RequiresOptIn`, add missing `@JvmStatic`/`@JvmOverloads`, complete
KDoc on every public symbol.
4. **Coroutine hygiene fourth.** Remove `GlobalScope`, inject
dispatchers, convert callback APIs to `suspend`/`Flow`, move blocking
IO under `withContext(io)` at the correct level.
5. **Final tightening.** Turn temporary warnings into build failures.
Detekt limits, BCV diff, lint, `-Werror` all gate CI.

Each step is its own commit (or small commit series) with a message
that names the rule from `KOTLIN_STYLE.md` being applied.

---

## Phase 3: Execution

For every file flagged in Phase 1:

- Apply the smallest change that satisfies the rule.
- Preserve behavior. Run the affected test suite after each file (not
the full suite — too slow; full suite at phase boundaries).
- When splitting a file, the new file names match their primary public
declaration. Update imports across the module.
- When decomposing a function, extracted helpers are `private` and
named for what they do, not how (`buildHeader`, not `helper1`).
- When replacing a public `data class`:
- Keep the same constructor signature.
- Hand-roll `equals` / `hashCode` / `toString` matching the data
class's previous output (verify with a snapshot test).
- Provide a `copy(...)` function with the same parameter list.
- Remove `componentN()` only after confirming no consumer (incl.
destructuring inside the SDK) depends on it; if any does, fix the
consumer first.
- When wrapping mutable collection returns: return `field.toList()` /
`.toMap()` / `.toSet()` at the boundary. Do not change internal
storage if it would degrade performance — wrap at the exit.
- When converting callbacks to `suspend`:
- Add the `suspend` version.
- Mark the callback version `@Deprecated(level = WARNING,
replaceWith = ReplaceWith("..."))`.
- Do not remove the callback in the same PR unless the project
versioning policy permits.

### Reducing complexity (apply mechanically)

When a function exceeds 30 lines or cyclomatic 10:

1. Identify the seams. A seam is any block delimited by a blank line,
a comment, or a logical phase (validate → fetch → transform →
persist → notify).
2. Extract each seam to a `private` function named for its phase.
3. Keep the outer function as a 5-to-10-line orchestrator that reads
like prose.
4. If after extraction the orchestrator still has a `when` or `if`
tree driving dispatch, replace it with a map lookup, sealed-class
dispatch, or polymorphism.

When a class exceeds 300 lines:

1. List its responsibilities by inspecting method clusters that share
state.
2. Extract each cluster to a collaborator. Inject via constructor.
3. The original class becomes a coordinator or is dissolved.

When a file exceeds 500 lines or contains 4+ public types:

1. Split by type. One public type per file unless types are tightly
coupled (sealed hierarchy, mutually recursive interface + impl).
2. Internal-only helpers stay with their primary user.

### Line length

- Aim for 80 chars. Hard cap 100.
- Break at logical boundaries: after `,` in long parameter lists, after
`=` for long expressions, after `.` for long call chains.
- Long string templates: extract to `private const val` or use
`trimIndent()` on a `"""` literal.
- Long generic signatures: extract a `typealias` if reused.

---

## Phase 4: Verification

Before declaring done, all of these must pass:

- [ ] `./gradlew build` clean, with `-Werror` enabled.
- [ ] `./gradlew detekt` clean against `KOTLIN_STYLE.md` §11 limits as
errors.
- [ ] `./gradlew ktlintCheck` (or `spotlessCheck`) clean.
- [ ] `./gradlew apiCheck` clean (BCV) — `.api` diffs explained in PR
description with rule citation.
- [ ] `./gradlew test` clean. Coverage on changed modules ≥ baseline.
- [ ] `grep -rn '!!' src/main` returns only lines with `// !!:`
justification comments.
- [ ] `grep -rn 'GlobalScope\|runBlocking' src/main` returns only
permitted locations (documented in PR).
- [ ] `grep -rn 'catch (.*: Exception)\|catch (.*: Throwable)' src/main`
returns zero matches or each is justified.
- [ ] No public symbol lacks KDoc. Verified by a Detekt rule or
lightweight script committed to the repo.
- [ ] No file exceeds 500 lines. No function exceeds 30. No class
exceeds 300. No function exceeds 5 parameters or nesting depth 3.
- [ ] Mean function length and mean file length both decrease vs.
baseline recorded in Phase 1.

---

## Completion Criteria

The task is complete when:

1. Every check in Phase 4 passes locally and in CI.
2. `REFACTOR_PLAN.md` is updated with a "Resolved" section listing
each violation from Phase 1 and the commit that addressed it.
3. The PR description summarizes: files changed, public API impact
(with `.api` diff if any), complexity reduction numbers
(mean/median/max before and after for function and file length),
and any blockers deferred with rationale.
4. A single squash-merge commit message names the style guide version
applied (`Conform to KOTLIN_STYLE.md @ <sha>`).

---

## Output Discipline

- Do not narrate progress in chat. Commit messages and the PR
description are the report.
- Do not summarize the style guide back. Apply it.
- Do not output the full file after each edit. Show diffs only when
asked.
- The final message at task completion is a single paragraph: branch
name, PR link or `gh pr create` command, headline metrics
(files changed, complexity delta, API stability status). Nothing
else.

---

## On Disagreement With the Style Guide

If, during execution, a rule in `KOTLIN_STYLE.md` produces a clearly
worse result for a specific case (rare), do not silently deviate.

1. Apply the rule.
2. Note the case in `STYLE_GUIDE_FEEDBACK.md` at the repo root with
file, line, rule, observed downside, and proposed amendment.
3. Continue.

The style guide evolves through that file, not through ad-hoc
exceptions in code.
Loading
Loading