Pure-Kotlin classifier for feature/codebrowser/content/ that turns already-loaded SourceContent bytes into bounded text, binary, Git LFS pointer, unsupported-encoding, or oversized results with caller-supplied positive byte/character/line limits, per handoff/task-006a-ready. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
7.1 KiB
TASK-006A: Classify source content safely
Status: implemented by Claude; pending Codex read-only review Owner: Claude Reviewer: Codex
Outcome
Classify already-loaded repository content into bounded text, binary, Git LFS pointer, unsupported encoding, or oversized states so the future code viewer never blindly decodes or renders arbitrary bytes.
Contract freeze
- Shared domain contract commit:
0929282. - Reviewed handoff package commit:
57d1c09. Claude starts from immutable activation taghandoff/task-006a-ready; no shared domain file may differ from0929282without an approved contract change. - Input contracts:
SourceContent,SourceFile,RepositoryPath, andRepositoryReffrom TASK-003. - This task must not change the frozen domain contracts. Required changes use the request format in
docs/workstreams.md.
Writable paths
app/src/main/java/com/ubisam/ubgitea/feature/codebrowser/content/app/src/test/java/com/ubisam/ubgitea/feature/codebrowser/content/tasks/006a-code-content-classifier.mdevidence and review sections onlydocs/development-log.mdappend-only TASK-006A completion/handoff entry only
Acceptance criteria
- Classification results are immutable and distinguish text, binary, Git LFS pointer, unsupported encoding, and oversized content.
- Maximum input bytes, rendered characters, and rendered lines are constructor-injected positive limits.
- Oversized input is rejected before text decoding and exposes only safe metadata, not decoded content.
- Binary detection includes NUL bytes and does not convert arbitrary bytes to replacement characters.
- Text decoding uses a strict UTF-8 decoder that reports malformed or unmappable input.
- A Git LFS pointer is recognized only from its canonical version line and required object ID/size fields within configured bounds.
- Rendered text is explicitly marked truncated when character or line limits are exceeded.
- No Android, network, persistence, token, logging, or write-operation dependency appears in the package.
- Tests cover empty/ASCII/Korean text, CRLF, malformed UTF-8, NUL/binary, valid and malformed LFS pointers, exact/over byte limits, line limits, character limits, and defensive immutability.
Security and privacy
- Test fixtures must contain only synthetic public content.
- Classification errors must not include source content in messages.
- The classifier does not persist content or place it on the clipboard.
Required checks
./gradlew assembleDebug
./gradlew testDebugUnitTest
./gradlew lintDebug
./gradlew testDebugUnitTest --tests "com.ubisam.ubgitea.feature.codebrowser.content.*"
Handoff
Claude must provide the exact branch/commit, changed paths, acceptance-criteria evidence, check results, open findings, and a request for Codex review. No integration or shared-file edit is authorized by this task.
Evidence
Implemented on branch claude/task-006a-code-content-classifier from activation tag handoff/task-006a-ready (base commit 965d55d), using JDK 21 (Android Studio bundled JBR at D:\AndroidStudio\jbr) and Android SDK 36/Build-Tools 35.
- Files added (writable scope only):
app/src/main/java/com/ubisam/ubgitea/feature/codebrowser/content/ContentClassification.kt,app/src/main/java/com/ubisam/ubgitea/feature/codebrowser/content/SourceContentClassifier.kt,app/src/test/java/com/ubisam/ubgitea/feature/codebrowser/content/SourceContentClassifierTest.kt. - No shared domain, data, Gradle, manifest, navigation, or generic resource file was touched.
RepositoryModels.kt,RepositoryPath.kt, andRepositoryRef.ktunderdomain/model/remain byte-for-byte identical to contract-freeze commit0929282. - Design:
ContentClassificationis a sealed class of five data classes (Text,Binary,LfsPointer,UnsupportedEncoding,Oversized), each holding only primitive/String/Longfields, so no defensive copy is needed for immutability.SourceContentClassifiertakes three positive constructor-injected limits (maxInputBytes,maxRenderedCharacters,maxRenderedLines) and classifies in order: oversized (raw byte count vs. limit, before any decode) → binary (raw NUL-byte scan) → strict UTF-8 decode (CodingErrorAction.REPORTfor both malformed and unmappable input, so failures returnUnsupportedEncodinginstead of substituting replacement characters) → Git LFS pointer (exact canonicalversionline first, thenoid sha256:<64 hex>andsize <non-negative integer>lines, bounded to 1024 bytes per the upstream git-lfs spec) → bounded text with independent line-count and character-count truncation flags. - Acceptance-criteria checklist: all nine boxes above are satisfied — classification set is immutable and covers all five states; limits are constructor-injected and validated positive (
initblock); oversized content is rejected before decoding and exposes onlysizeBytes; binary detection uses a raw NUL scan ahead of decoding, and decoding never substitutes replacement characters; UTF-8 decoding is strict; LFS recognition requires the exact version line plus valid oid/size fields within the 1024-byte bound;Text.isTruncatedmarks both line- and character-limit overflow; the package imports onlycom.ubisam.ubgitea.domain.modelandjava.nio.*/Kotlin stdlib, no Android/network/persistence/token/logging/write dependency. - Test coverage (25 cases in
SourceContentClassifierTest): constructor limit validation (3), empty/ASCII/Korean text (3), CRLF preservation (1), NUL/binary (1), malformed and truncated-sequence UTF-8 (2), canonical and no-trailing-newline LFS pointers (2), malformed LFS pointers — wrong version, bad oid, non-numeric size, missing line (4), exact/over byte limit (2), oversized-and-binary precedence (1), exact/over line limit (2), exact/over character limit (2), defensive immutability against post-construction byte mutation and repeated classification (2). Only synthetic fixtures are used; no error path includes decoded source content in its result.
Checks run from the new worktree (local.properties created locally for the SDK path, not committed):
./gradlew testDebugUnitTest --tests "com.ubisam.ubgitea.feature.codebrowser.content.*" → BUILD SUCCESSFUL, 25/25 passed
./gradlew assembleDebug testDebugUnitTest lintDebug --rerun-tasks → BUILD SUCCESSFUL
- testDebugUnitTest: 82 tests total (repository-wide), 0 failures, 0 errors
- lintDebug: 0 errors, 22 pre-existing warnings (unrelated to this change; same count as TASK-001 baseline)
git diff --check → no whitespace errors
sensitive-pattern scan of changed files (secret/token/password/private-key/API-key markers) → no matches
Known limitations:
- No Android device/emulator was available; this task is pure JVM unit-testable Kotlin with no Android framework dependency, so no manual device step applies.
- Static formatting/check task is not configured for this module (unchanged from TASK-001).
No open P0-P3 findings from self-review against the acceptance criteria. Requesting Codex read-only review per docs/workstreams.md cross-review rules; Claude stops writing after this handoff.