From 0fb4ebb170c7cffe55c0cb46bfd1c3dc05a45ccd Mon Sep 17 00:00:00 2001 From: Kyle Date: Mon, 1 Sep 2025 04:20:30 +0800 Subject: [PATCH 1/5] Update missing Observation implementation. --- CLAUDE.md | 11 +++--- .../Platform/PlatformViewRepresentable.swift | 35 ++++++++++--------- .../EnvironmentKeyTransformModifier.swift | 8 ++--- .../PreferenceTransformModifier.swift | 7 ++-- .../Layout/Geometry/GeometryReader.swift | 7 ++-- 5 files changed, 38 insertions(+), 30 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index a3a2bf2c8..ed7368513 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -136,10 +136,11 @@ The Package.swift heavily uses environment variables for conditional compilation ### Commit Workflow When asked to commit changes: -1. **Generate a commit message** and provide it to the user -2. **List the files to be committed** (specify if not all files) -3. **Let the user commit through GitBulter GUI** - do not run git commit commands -4. Wait for user confirmation before proceeding with any PR creation +1. **Generate a commit message** and provide it to the user (formatted without extra spaces) +2. **Save the commit message** to `.claude/tmp/commit-message.md` for easy copying +3. **List the files to be committed** (specify if not all files) +4. **Let the user commit through GitBulter GUI** - do not run git commit commands +5. Wait for user confirmation before proceeding with any PR creation Example response when asked to commit: ``` @@ -154,6 +155,8 @@ Here's the commit message for you to use in GitBulter: Files to commit: - src/feature.swift - tests/feature_test.swift + +(Saved to .claude/tmp/commit-message.md) ``` ### Pull Request Workflow diff --git a/Sources/OpenSwiftUI/Integration/Representable/Platform/PlatformViewRepresentable.swift b/Sources/OpenSwiftUI/Integration/Representable/Platform/PlatformViewRepresentable.swift index bef0b98c0..878ec9adf 100644 --- a/Sources/OpenSwiftUI/Integration/Representable/Platform/PlatformViewRepresentable.swift +++ b/Sources/OpenSwiftUI/Integration/Representable/Platform/PlatformViewRepresentable.swift @@ -232,24 +232,25 @@ struct PlatformViewChild: StatefulRule { } if platformView == nil { let host = ViewGraph.viewRendererHost - // TODO: StatefulRule.withObservation - Graph.withoutUpdate { - let representableContext = PlatformViewRepresentableContext( - coordinator: coordinator!, - preferenceBridge: bridge, - transaction: transaction, - environmentStorage: .eager(environment) - ) - representableContext.values.asCurrent { - let provider = view.makeViewProvider(context: representableContext) - let environment = environment.removingTracker() - platformView = PlatformViewHost( - provider, - host: host, - environment: environment, - viewPhase: phase, - importer: importer + withObservation { + Graph.withoutUpdate { + let representableContext = PlatformViewRepresentableContext( + coordinator: coordinator!, + preferenceBridge: bridge, + transaction: transaction, + environmentStorage: .eager(environment) ) + representableContext.values.asCurrent { + let provider = view.makeViewProvider(context: representableContext) + let environment = environment.removingTracker() + platformView = PlatformViewHost( + provider, + host: host, + environment: environment, + viewPhase: phase, + importer: importer + ) + } } } } diff --git a/Sources/OpenSwiftUICore/Data/Environment/EnvironmentKeyTransformModifier.swift b/Sources/OpenSwiftUICore/Data/Environment/EnvironmentKeyTransformModifier.swift index 5fb5b9290..79c532647 100644 --- a/Sources/OpenSwiftUICore/Data/Environment/EnvironmentKeyTransformModifier.swift +++ b/Sources/OpenSwiftUICore/Data/Environment/EnvironmentKeyTransformModifier.swift @@ -3,7 +3,7 @@ // OpenSwiftUICore // // Audited for iOS 18.0 -// Status: Blocked by Observation +// Status: Complete // ID: 1DBD4F024EFF0E73A70DB6DD05D5B548 (SwiftUI) // ID: E370275CDB55AC7AD9ACF0420859A9E8 (SwiftUICore) @@ -63,14 +63,14 @@ private struct ChildEnvironment: StatefulRule, AsyncAttribute, CustomStri typealias Value = EnvironmentValues - // FIXME mutating func updateValue() { var (environment, environmentChanged) = _environment.changedValue() let keyPath = modifier.keyPath var newValue = environment[keyPath: keyPath] $modifier.syncMainIfReferences { modifier in - // TODO: Observation - modifier.transform(&newValue) + withObservation { + modifier.transform(&newValue) + } } guard !environmentChanged, let valueChanged = oldValue.map({ compareValues($0, newValue, mode: .equatableUnlessPOD) }), !valueChanged, diff --git a/Sources/OpenSwiftUICore/Data/Preference/PreferenceTransformModifier.swift b/Sources/OpenSwiftUICore/Data/Preference/PreferenceTransformModifier.swift index 5d4b1ece7..bc2f51f26 100644 --- a/Sources/OpenSwiftUICore/Data/Preference/PreferenceTransformModifier.swift +++ b/Sources/OpenSwiftUICore/Data/Preference/PreferenceTransformModifier.swift @@ -88,7 +88,7 @@ extension PreferencesOutputs { } } -// MARK: - PreferenceTransform [6.0.87] +// MARK: - PreferenceTransform [6.5.4] private struct PreferenceTransform: Rule, AsyncAttribute, CustomStringConvertible where K: PreferenceKey { @Attribute var transform: (inout K.Value) -> Void @@ -97,8 +97,9 @@ private struct PreferenceTransform: Rule, AsyncAttribute, CustomStringConvert var value: K.Value { var value = childValue ?? K.defaultValue $transform.syncMainIfReferences { transform in - // TODO: Observation support - transform(&value) + withObservation { + transform(&value) + } } return value } diff --git a/Sources/OpenSwiftUICore/Layout/Geometry/GeometryReader.swift b/Sources/OpenSwiftUICore/Layout/Geometry/GeometryReader.swift index bdc1deee5..c77c9457e 100644 --- a/Sources/OpenSwiftUICore/Layout/Geometry/GeometryReader.swift +++ b/Sources/OpenSwiftUICore/Layout/Geometry/GeometryReader.swift @@ -88,8 +88,11 @@ public struct GeometryReader: View, UnaryView, PrimitiveView where Cont safeAreaInsets: $safeAreaInsets, seed: seed, ) - // TODO: Observation - let content = view.content(proxy) + let content = withObservation { + $view.syncMainIfReferences { v in + v.content(proxy) + } + } value = .init(root: .init(GeometryReaderLayout()), content: content) } } From f4fa10814943a06c68e9f7fa8f4f0b6e51d0f83b Mon Sep 17 00:00:00 2001 From: Kyle Date: Sat, 6 Sep 2025 15:14:04 +0800 Subject: [PATCH 2/5] Fix Observation issue --- Example/HostingExample/ViewController.swift | 2 +- .../Data/ObservationExample.swift | 38 +++++++++++++++++++ Package.resolved | 6 +-- .../DynamicProperty/DynamicProperty.swift | 24 ++++++++---- ...ationUtil.swift => ObservationUtils.swift} | 4 +- ...ests.swift => ObservationUtilsTests.swift} | 8 +--- 6 files changed, 63 insertions(+), 19 deletions(-) create mode 100644 Example/SharedExample/Data/ObservationExample.swift rename Sources/OpenSwiftUICore/Data/Observation/{ObservationUtil.swift => ObservationUtils.swift} (98%) rename Tests/OpenSwiftUICoreTests/Data/Observation/{ObservationUtilTests.swift => ObservationUtilsTests.swift} (93%) diff --git a/Example/HostingExample/ViewController.swift b/Example/HostingExample/ViewController.swift index 44bbec402..77fadfd40 100644 --- a/Example/HostingExample/ViewController.swift +++ b/Example/HostingExample/ViewController.swift @@ -66,6 +66,6 @@ class ViewController: NSViewController { struct ContentView: View { var body: some View { - GeometryReaderExample() + ObservationExample() } } diff --git a/Example/SharedExample/Data/ObservationExample.swift b/Example/SharedExample/Data/ObservationExample.swift new file mode 100644 index 000000000..df372bc01 --- /dev/null +++ b/Example/SharedExample/Data/ObservationExample.swift @@ -0,0 +1,38 @@ +// +// ObservationExample.swift +// SharedExample + +#if OPENSWIFTUI +import OpenObservation +import OpenSwiftUI +#else +import Observation +import SwiftUI +#endif + +@Observable +private class Model { + var showRed = false +} + +struct ObservationExample: View { + @State private var model = Model() + + private var showRed: Bool { + get { model.showRed } + nonmutating set { model.showRed = newValue } + } + + var body: some View { + VStack { + Color(platformColor: showRed ? .red : .blue) + .frame(width: showRed ? 200 : 400, height: showRed ? 200 : 400) + } + .animation(.spring, value: showRed) + .onAppear { + DispatchQueue.main.asyncAfter(deadline: .now() + 1) { + showRed.toggle() + } + } + } +} diff --git a/Package.resolved b/Package.resolved index cb37a0743..7267664d6 100644 --- a/Package.resolved +++ b/Package.resolved @@ -7,7 +7,7 @@ "location" : "https://github.com/OpenSwiftUIProject/DarwinPrivateFrameworks.git", "state" : { "branch" : "main", - "revision" : "00977e88d14f98c248bf1c8370855ad6ecc35977" + "revision" : "082d0abd12876636df553723e22a395a881f5491" } }, { @@ -16,7 +16,7 @@ "location" : "https://github.com/OpenSwiftUIProject/OpenAttributeGraph", "state" : { "branch" : "main", - "revision" : "d0a060c05377350dea911601dc5416738c000541" + "revision" : "989275d31790336e71080829256022b83e97a6ba" } }, { @@ -34,7 +34,7 @@ "location" : "https://github.com/OpenSwiftUIProject/OpenObservation", "state" : { "branch" : "main", - "revision" : "1eb29e56b75f86cf7ee1593e315bafc1ab07be3d" + "revision" : "1dd8b16a539e3563e05b77749acf334a4857a6a7" } }, { diff --git a/Sources/OpenSwiftUICore/Data/DynamicProperty/DynamicProperty.swift b/Sources/OpenSwiftUICore/Data/DynamicProperty/DynamicProperty.swift index 711324d20..445734e90 100644 --- a/Sources/OpenSwiftUICore/Data/DynamicProperty/DynamicProperty.swift +++ b/Sources/OpenSwiftUICore/Data/DynamicProperty/DynamicProperty.swift @@ -309,8 +309,11 @@ private struct StaticBody extension StaticBody: StatefulRule { typealias Value = Accessor.Body + // Audited with 6.5.4 func updateValue() { - accessor.updateBody(of: container, changed: true) + withObservation { + accessor.updateBody(of: container, changed: true) + } } static var flags: Flags { ThreadFlags.value } @@ -370,19 +373,26 @@ private struct DynamicBody extension DynamicBody: StatefulRule { typealias Value = Accessor.Body + // Audited with 6.5.4 mutating func updateValue() { if resetSeed != phase.resetSeed { links.reset() resetSeed = phase.resetSeed } - var (container, containerChanged) = $container.changedValue() - let linkChanged = withUnsafeMutablePointer(to: &container) { - links.update(container: $0, phase: phase) + var (container, changed) = $container.changedValue() + withObservation { + withUnsafeMutablePointer(to: &container) { + if links.update(container: $0, phase: phase) { + changed = true + } + } + accessor.updateBody( + of: container, + changed: changed || !hasValue || AnyAttribute.currentWasModified + ) } - let changed = linkChanged || containerChanged || !hasValue - accessor.updateBody(of: container, changed: changed) } - + static var flags: Flags { ThreadFlags.value } } diff --git a/Sources/OpenSwiftUICore/Data/Observation/ObservationUtil.swift b/Sources/OpenSwiftUICore/Data/Observation/ObservationUtils.swift similarity index 98% rename from Sources/OpenSwiftUICore/Data/Observation/ObservationUtil.swift rename to Sources/OpenSwiftUICore/Data/Observation/ObservationUtils.swift index 4d3bfd444..bc99f5da2 100644 --- a/Sources/OpenSwiftUICore/Data/Observation/ObservationUtil.swift +++ b/Sources/OpenSwiftUICore/Data/Observation/ObservationUtils.swift @@ -1,10 +1,10 @@ // -// ObservationUtil.swift +// ObservationUtils.swift // OpenSwiftUICore // // Audited for 6.5.4 // Status: Blocked by TraceEvent -// ID: 7DF024579E4FC31D4E92A33BBA0366D6 (SwiftUI?) +// ID: 7DF024579E4FC31D4E92A33BBA0366D6 (SwiftUICore) import Foundation package import OpenAttributeGraphShims diff --git a/Tests/OpenSwiftUICoreTests/Data/Observation/ObservationUtilTests.swift b/Tests/OpenSwiftUICoreTests/Data/Observation/ObservationUtilsTests.swift similarity index 93% rename from Tests/OpenSwiftUICoreTests/Data/Observation/ObservationUtilTests.swift rename to Tests/OpenSwiftUICoreTests/Data/Observation/ObservationUtilsTests.swift index 44f4b69d5..681eebcbc 100644 --- a/Tests/OpenSwiftUICoreTests/Data/Observation/ObservationUtilTests.swift +++ b/Tests/OpenSwiftUICoreTests/Data/Observation/ObservationUtilsTests.swift @@ -1,7 +1,6 @@ // -// ObservationUtilTests.swift +// ObservationUtilsTests.swift // OpenSwiftUICoreTests -// import Testing import OpenAttributeGraphShims @@ -39,10 +38,7 @@ struct ObservationUtilTests { } @MainActor - @Test( - "_withObservation with attribute installation", - .disabled("retain a invalid ptr and cause crash. Investigate it later.") // TODO - ) + @Test("_withObservation with attribute installation") func withObservationAttribute() { let model = TestModel() let viewGraph = ViewGraph(rootViewType: EmptyView.self) From c4a80163fa3af22064b067a7af805aaaf337966b Mon Sep 17 00:00:00 2001 From: Kyle Date: Sat, 6 Sep 2025 15:35:51 +0800 Subject: [PATCH 3/5] Add compatibility tests for IDView and ObservationUtils --- .../ObservationUtilsCompatibilityTests.swift | 53 +++++++++++++ .../Export.swift | 2 + .../View/IDViewCompatibilityTests.swift | 77 +++++++++++++++++++ 3 files changed, 132 insertions(+) create mode 100644 Tests/OpenSwiftUICompatibilityTests/Data/Observation/ObservationUtilsCompatibilityTests.swift create mode 100644 Tests/OpenSwiftUICompatibilityTests/View/IDViewCompatibilityTests.swift diff --git a/Tests/OpenSwiftUICompatibilityTests/Data/Observation/ObservationUtilsCompatibilityTests.swift b/Tests/OpenSwiftUICompatibilityTests/Data/Observation/ObservationUtilsCompatibilityTests.swift new file mode 100644 index 000000000..a395970c1 --- /dev/null +++ b/Tests/OpenSwiftUICompatibilityTests/Data/Observation/ObservationUtilsCompatibilityTests.swift @@ -0,0 +1,53 @@ +// +// ObservationUtilsCompatibilityTests.swift +// OpenSwiftUICompatibilityTests + +import OpenSwiftUITestsSupport +import Testing + +@MainActor +struct ObservationUtilsCompatibilityTests { + @Observable + class Model { + var showRed = false + } + + struct ContentView: View { + let confirmation: Confirmation + var continuation: UnsafeContinuation + + @State private var model = Model() + + private var showRed: Bool { + get { model.showRed } + nonmutating set { model.showRed = newValue } + } + + var body: some View { + let _ = confirmation() + Color(platformColor: showRed ? .red : .blue) + .onAppear { + DispatchQueue.main.asyncAfter(deadline: .now() + 1) { + if model.showRed { + continuation.resume() + } else { + showRed.toggle() + } + } + } + .id(showRed) + } + } + + @Test + func bodyTrigger() async throws { + try await triggerLayoutWithWindow(expectedCount: 2) { confirmation, continuation in + PlatformHostingController( + rootView: ContentView( + confirmation: confirmation, + continuation: continuation + ) + ) + } + } +} diff --git a/Tests/OpenSwiftUICompatibilityTests/Export.swift b/Tests/OpenSwiftUICompatibilityTests/Export.swift index 444ddde3c..9a3e24cfb 100644 --- a/Tests/OpenSwiftUICompatibilityTests/Export.swift +++ b/Tests/OpenSwiftUICompatibilityTests/Export.swift @@ -3,9 +3,11 @@ // OpenSwiftUICompatibilityTests #if OPENSWIFTUI +@_exported import OpenObservation @_exported import OpenSwiftUI let compatibilityTestEnabled = false #else +@_exported import Observation @_exported import SwiftUI let compatibilityTestEnabled = true #endif diff --git a/Tests/OpenSwiftUICompatibilityTests/View/IDViewCompatibilityTests.swift b/Tests/OpenSwiftUICompatibilityTests/View/IDViewCompatibilityTests.swift new file mode 100644 index 000000000..16ee4ea87 --- /dev/null +++ b/Tests/OpenSwiftUICompatibilityTests/View/IDViewCompatibilityTests.swift @@ -0,0 +1,77 @@ +// +// IDViewCompatibilityTests.swift +// OpenSwiftUICompatibilityTests + +import OpenSwiftUITestsSupport +import Testing + +@MainActor +struct IDViewCompatibilityTests { + @Test("onAppear will be called again when the view's id changes if id modifier is applied after onAppear modifier") + func idAfterOnAppear() async throws { + struct ContentView: View { + let confirmation: Confirmation + var continuation: UnsafeContinuation + + @State private var showRed = false + + var body: some View { + Color(platformColor: showRed ? .red : .blue) + .onAppear { + let _ = confirmation() + DispatchQueue.main.asyncAfter(deadline: .now() + 1) { + if showRed { + continuation.resume() + } else { + showRed.toggle() + } + } + } + .id(showRed) + } + } + try await triggerLayoutWithWindow(expectedCount: 2) { confirmation, continuation in + PlatformHostingController( + rootView: ContentView( + confirmation: confirmation, + continuation: continuation + ) + ) + } + } + + @Test("onAppear will not be called again when the view's id changes if id modifier is applied before onAppear modifier") + func idBeforeOnAppear() async throws { + struct ContentView: View { + let confirmation: Confirmation + var continuation: UnsafeContinuation + + @State private var showRed = false + + var body: some View { + Color(platformColor: showRed ? .red : .blue) + .onAppear { + DispatchQueue.main.asyncAfter(deadline: .now() + 1) { + if showRed { + continuation.resume() + } else { + showRed.toggle() + } + } + } + .id(showRed) + .onAppear { + let _ = confirmation() + } + } + } + try await triggerLayoutWithWindow(expectedCount: 1) { confirmation, continuation in + PlatformHostingController( + rootView: ContentView( + confirmation: confirmation, + continuation: continuation + ) + ) + } + } +} From db230d8f90b71488c3a105f6b3ccab65499092b7 Mon Sep 17 00:00:00 2001 From: Kyle Date: Sat, 6 Sep 2025 15:55:11 +0800 Subject: [PATCH 4/5] Fix Linux build issue --- Sources/OpenSwiftUI/Util/AnyAttributeFix.swift | 2 ++ Sources/OpenSwiftUICore/Util/AnyAttributeFix.swift | 2 ++ .../Data/Observation/ObservationUtilsTests.swift | 4 +++- 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/Sources/OpenSwiftUI/Util/AnyAttributeFix.swift b/Sources/OpenSwiftUI/Util/AnyAttributeFix.swift index 158949cbb..ff80e79c8 100644 --- a/Sources/OpenSwiftUI/Util/AnyAttributeFix.swift +++ b/Sources/OpenSwiftUI/Util/AnyAttributeFix.swift @@ -13,6 +13,8 @@ package typealias AttributeType = OpenSwiftUICore.AttributeType extension AnyAttribute { package static var `nil`: AnyAttribute { AnyAttribute(rawValue: 0x2) } + package static var currentWasModified: Bool { false } + package var source: AnyAttribute? { get { preconditionFailure("#39") } nonmutating set { preconditionFailure("#39") } diff --git a/Sources/OpenSwiftUICore/Util/AnyAttributeFix.swift b/Sources/OpenSwiftUICore/Util/AnyAttributeFix.swift index 337f1e24e..d8dfc9efa 100644 --- a/Sources/OpenSwiftUICore/Util/AnyAttributeFix.swift +++ b/Sources/OpenSwiftUICore/Util/AnyAttributeFix.swift @@ -60,6 +60,8 @@ package struct AttributeType { extension AnyAttribute { package static var `nil`: AnyAttribute { AnyAttribute(rawValue: 0x2) } + package static var currentWasModified: Bool { false } + package var source: AnyAttribute? { get { preconditionFailure("#39") } nonmutating set { preconditionFailure("#39") } diff --git a/Tests/OpenSwiftUICoreTests/Data/Observation/ObservationUtilsTests.swift b/Tests/OpenSwiftUICoreTests/Data/Observation/ObservationUtilsTests.swift index 681eebcbc..f5ba96a89 100644 --- a/Tests/OpenSwiftUICoreTests/Data/Observation/ObservationUtilsTests.swift +++ b/Tests/OpenSwiftUICoreTests/Data/Observation/ObservationUtilsTests.swift @@ -37,6 +37,7 @@ struct ObservationUtilTests { #expect(accessList != nil) } + #if canImport(Darwin) @MainActor @Test("_withObservation with attribute installation") func withObservationAttribute() { @@ -82,7 +83,8 @@ struct ObservationUtilTests { #expect(outerResult == 50) #expect(outerAccessList != nil) } - + #endif + @Test("ObservationRegistrar latestAccessLists tracking") func latestAccessListsTracking() { let model = TestModel() From 453b3463b1c02a4afbf9d56884be30c5a98f19c1 Mon Sep 17 00:00:00 2001 From: Kyle Date: Sat, 6 Sep 2025 16:47:31 +0800 Subject: [PATCH 5/5] Update workflow --- .github/workflows/claude-code-review.yml | 54 ----------------- .github/workflows/issue-triage.yml | 76 ++++++++++++++++++++++++ 2 files changed, 76 insertions(+), 54 deletions(-) delete mode 100644 .github/workflows/claude-code-review.yml create mode 100644 .github/workflows/issue-triage.yml diff --git a/.github/workflows/claude-code-review.yml b/.github/workflows/claude-code-review.yml deleted file mode 100644 index 4caf96a2f..000000000 --- a/.github/workflows/claude-code-review.yml +++ /dev/null @@ -1,54 +0,0 @@ -name: Claude Code Review - -on: - pull_request: - types: [opened, synchronize] - # Optional: Only run on specific file changes - # paths: - # - "src/**/*.ts" - # - "src/**/*.tsx" - # - "src/**/*.js" - # - "src/**/*.jsx" - -jobs: - claude-review: - # Optional: Filter by PR author - # if: | - # github.event.pull_request.user.login == 'external-contributor' || - # github.event.pull_request.user.login == 'new-developer' || - # github.event.pull_request.author_association == 'FIRST_TIME_CONTRIBUTOR' - - runs-on: ubuntu-latest - permissions: - contents: read - pull-requests: read - issues: read - id-token: write - - steps: - - name: Checkout repository - uses: actions/checkout@v4 - with: - fetch-depth: 1 - - - name: Run Claude Code Review - id: claude-review - uses: anthropics/claude-code-action@v1 - with: - claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }} - prompt: | - Please review this pull request and provide feedback on: - - Code quality and best practices - - Potential bugs or issues - - Performance considerations - - Security concerns - - Test coverage - - Use the repository's CLAUDE.md for guidance on style and conventions. Be constructive and helpful in your feedback. - - Use `gh pr comment` with your Bash tool to leave your review as a comment on the PR. - - # See https://github.com/anthropics/claude-code-action/blob/main/docs/usage.md - # or https://docs.anthropic.com/en/docs/claude-code/sdk#command-line for available options - claude_args: '--allowed-tools "Bash(gh issue view:*),Bash(gh search:*),Bash(gh issue list:*),Bash(gh pr comment:*),Bash(gh pr diff:*),Bash(gh pr view:*),Bash(gh pr list:*)"' - diff --git a/.github/workflows/issue-triage.yml b/.github/workflows/issue-triage.yml new file mode 100644 index 000000000..6029d8596 --- /dev/null +++ b/.github/workflows/issue-triage.yml @@ -0,0 +1,76 @@ +name: Issue Triage +on: + issues: + types: [opened] + +jobs: + triage-issue: + runs-on: ubuntu-latest + timeout-minutes: 10 + permissions: + contents: read + issues: write + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Triage issue with Claude + uses: anthropics/claude-code-action@v1 + with: + prompt: | + You're an issue triage assistant for GitHub issues. Your task is to analyze the issue and select appropriate labels from the provided list. + + IMPORTANT: Don't post any comments or messages to the issue. Your only action should be to apply labels. + + Issue Information: + - REPO: ${{ github.repository }} + - ISSUE_NUMBER: ${{ github.event.issue.number }} + + TASK OVERVIEW: + + 1. First, fetch the list of labels available in this repository by running: `gh label list`. Run exactly this command with nothing else. + + 2. Next, use the GitHub tools to get context about the issue: + - You have access to these tools: + - mcp__github__get_issue: Use this to retrieve the current issue's details including title, description, and existing labels + - mcp__github__get_issue_comments: Use this to read any discussion or additional context provided in the comments + - mcp__github__update_issue: Use this to apply labels to the issue (do not use this for commenting) + - mcp__github__search_issues: Use this to find similar issues that might provide context for proper categorization and to identify potential duplicate issues + - mcp__github__list_issues: Use this to understand patterns in how other issues are labeled + - Start by using mcp__github__get_issue to get the issue details + + 3. Analyze the issue content, considering: + - The issue title and description + - The type of issue (bug report, feature request, question, etc.) + - Technical areas mentioned + - Severity or priority indicators + - User impact + - Components affected + + 4. Select appropriate labels from the available labels list provided above: + - Choose labels that accurately reflect the issue's nature + - Be specific but comprehensive + - Select priority labels if you can determine urgency (high-priority, med-priority, or low-priority) + - Consider platform labels (android, ios) if applicable + - If you find similar issues using mcp__github__search_issues, consider using a "duplicate" label if appropriate. Only do so if the issue is a duplicate of another OPEN issue. + + 5. Apply the selected labels: + - Use mcp__github__update_issue to apply your selected labels + - DO NOT post any comments explaining your decision + - DO NOT communicate directly with users + - If no labels are clearly applicable, do not apply any labels + + IMPORTANT GUIDELINES: + - Be thorough in your analysis + - Only select labels from the provided list above + - DO NOT post any comments to the issue + - Your ONLY action should be to apply labels using mcp__github__update_issue + - It's okay to not add any labels if none are clearly applicable + + anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }} + github_token: ${{ secrets.GITHUB_TOKEN }} + claude_args: | + --allowedTools "Bash(gh label list),mcp__github__get_issue,mcp__github__get_issue_comments,mcp__github__update_issue,mcp__github__search_issues,mcp__github__list_issues" \ No newline at end of file