From ff576acff994abf2895566dba5f289c69fb8bb7e Mon Sep 17 00:00:00 2001 From: Leo Dion Date: Wed, 17 Jun 2026 19:40:40 -0400 Subject: [PATCH 1/3] ConfigKeyKit: add ConfigValueReading (issue #1, dep-free core) Move the CLI->ENV->default source-precedence resolution that downstream consumers hand-wrote as `extension ConfigReader { read(_:) }` into the Foundation-only core behind a `ConfigValueReading` protocol. An associated `Key` type mirrors swift-configuration's ConfigReader read surface so a consumer conforms in one line (`makeConfigKey`), and the read(_:) overloads (String/Bool/Double required; String/Int/Double/Date optional) are shared and unit-tested via a mock conformer. Co-Authored-By: Claude Opus 4.8 (1M context) --- Sources/ConfigKeyKit/ConfigValueReading.swift | 164 ++++++++++++++++++ .../ConfigValueReadingTests.swift | 134 ++++++++++++++ 2 files changed, 298 insertions(+) create mode 100644 Sources/ConfigKeyKit/ConfigValueReading.swift create mode 100644 Tests/ConfigKeyKitTests/ConfigValueReadingTests.swift diff --git a/Sources/ConfigKeyKit/ConfigValueReading.swift b/Sources/ConfigKeyKit/ConfigValueReading.swift new file mode 100644 index 0000000..c57a6d0 --- /dev/null +++ b/Sources/ConfigKeyKit/ConfigValueReading.swift @@ -0,0 +1,164 @@ +// +// ConfigValueReading.swift +// ConfigKeyKit +// +// Created by Leo Dion. +// Copyright © 2026 BrightDigit. +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +public import Foundation + +/// A reader that resolves ``ConfigKey`` / ``OptionalConfigKey`` values across +/// every ``ConfigKeySource`` in precedence order. +/// +/// This protocol holds the source-precedence resolution that downstream +/// consumers previously hand-wrote as an `extension ConfigReader { read(_:) }` +/// (see issue #1, "Remove Need for Extension"). The logic lives here, in +/// ConfigKeyKit's Foundation-only core, so it is shared and unit-testable with a +/// trivial mock — no configuration framework required. +/// +/// The three primitive requirements mirror the read surface of +/// `swift-configuration`'s `ConfigReader` exactly, so a consumer conforms in a +/// single line: +/// +/// ```swift +/// extension ConfigReader: @retroactive ConfigValueReading { +/// public func makeConfigKey(_ s: String) -> Configuration.ConfigKey { .init(s) } +/// } +/// ``` +/// +/// `string(forKey:isSecret:fileID:line:)`, `int(...)`, and `double(...)` are +/// then witnessed by `ConfigReader`'s own methods, and ``Key`` infers to +/// `Configuration.ConfigKey`. +public protocol ConfigValueReading { + /// The reader's native key type (e.g. `Configuration.ConfigKey`). + associatedtype Key + + /// Builds a native ``Key`` from a resolved per-source key string. + func makeConfigKey(_ string: String) -> Key + + /// Reads a string value for the native key, or `nil` if absent. + func string(forKey key: Key, isSecret: Bool, fileID: String, line: UInt) -> String? + + /// Reads an integer value for the native key, or `nil` if absent. + func int(forKey key: Key, isSecret: Bool, fileID: String, line: UInt) -> Int? + + /// Reads a double value for the native key, or `nil` if absent. + func double(forKey key: Key, isSecret: Bool, fileID: String, line: UInt) -> Double? +} + +extension ConfigValueReading { + /// Reads a required string value: CLI → ENV → the key's default. + public func read(_ key: ConfigKey) -> String { + resolvedString(key) ?? key.defaultValue + } + + /// Reads a required double value: CLI → ENV → the key's default. + public func read(_ key: ConfigKey) -> Double { + resolvedDouble(key) ?? key.defaultValue + } + + /// Reads a required boolean value. + /// + /// - CLI: flag presence indicates `true` (e.g. `--verbose`). + /// - ENV: accepts `true` / `1` / `yes` (case-insensitive); empty is absent. + /// - Otherwise the key's default. + public func read(_ key: ConfigKey) -> Bool { + if let cli = key.key(for: .commandLine), + string(forKey: makeConfigKey(cli), isSecret: false, fileID: #fileID, line: #line) != nil + { + return true + } + if let env = key.key(for: .environment), + let value = string( + forKey: makeConfigKey(env), isSecret: false, fileID: #fileID, line: #line + ) + { + let normalized = value.lowercased().trimmingCharacters(in: .whitespaces) + return normalized == "true" || normalized == "1" || normalized == "yes" + } + return key.defaultValue + } + + /// Reads an optional string value: CLI → ENV → `nil`. + public func read(_ key: OptionalConfigKey) -> String? { + resolvedString(key) + } + + /// Reads an optional integer value: CLI → ENV → `nil`. + public func read(_ key: OptionalConfigKey) -> Int? { + resolvedInt(key) + } + + /// Reads an optional double value: CLI → ENV → `nil`. + public func read(_ key: OptionalConfigKey) -> Double? { + resolvedDouble(key) + } + + /// Reads an optional ISO8601 date value: CLI → ENV → `nil`. + public func read(_ key: OptionalConfigKey) -> Date? { + guard let value = resolvedString(key) else { + return nil + } + return ISO8601DateFormatter().date(from: value) + } + + // MARK: - Source-precedence resolution + + private func resolvedString(_ key: any ConfigurationKey) -> String? { + for source in ConfigKeySource.allCases { + guard let keyString = key.key(for: source) else { continue } + if let value = string( + forKey: makeConfigKey(keyString), isSecret: false, fileID: #fileID, line: #line + ) { + return value + } + } + return nil + } + + private func resolvedInt(_ key: any ConfigurationKey) -> Int? { + for source in ConfigKeySource.allCases { + guard let keyString = key.key(for: source) else { continue } + if let value = int( + forKey: makeConfigKey(keyString), isSecret: false, fileID: #fileID, line: #line + ) { + return value + } + } + return nil + } + + private func resolvedDouble(_ key: any ConfigurationKey) -> Double? { + for source in ConfigKeySource.allCases { + guard let keyString = key.key(for: source) else { continue } + if let value = double( + forKey: makeConfigKey(keyString), isSecret: false, fileID: #fileID, line: #line + ) { + return value + } + } + return nil + } +} diff --git a/Tests/ConfigKeyKitTests/ConfigValueReadingTests.swift b/Tests/ConfigKeyKitTests/ConfigValueReadingTests.swift new file mode 100644 index 0000000..e671560 --- /dev/null +++ b/Tests/ConfigKeyKitTests/ConfigValueReadingTests.swift @@ -0,0 +1,134 @@ +// +// ConfigValueReadingTests.swift +// ConfigKeyKit +// +// Created by Leo Dion. +// Copyright © 2026 BrightDigit. +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +import Foundation +import Testing + +@testable import ConfigKeyKit + +/// Dict-backed mock reader, keyed by the exact per-source key strings that +/// `ConfigKey`/`OptionalConfigKey` produce. No configuration framework needed. +private struct MockReader: ConfigValueReading { + var strings: [String: String] = [:] + var ints: [String: Int] = [:] + var doubles: [String: Double] = [:] + + func makeConfigKey(_ string: String) -> String { string } + + func string(forKey key: String, isSecret _: Bool, fileID _: String, line _: UInt) -> String? { + strings[key] + } + + func int(forKey key: String, isSecret _: Bool, fileID _: String, line _: UInt) -> Int? { + ints[key] + } + + func double(forKey key: String, isSecret _: Bool, fileID _: String, line _: UInt) -> Double? { + doubles[key] + } +} + +@Suite("ConfigValueReading Tests") +internal struct ConfigValueReadingTests { + private let key = ConfigKey("base-url", envPrefix: "BRIGHTDIGIT", default: "default-url") + private var cliKey: String { key.key(for: .commandLine)! } + private var envKey: String { key.key(for: .environment)! } + + @Test("Required string: CLI wins over ENV") + internal func requiredStringCLIPrecedence() { + let reader = MockReader(strings: [cliKey: "from-cli", envKey: "from-env"]) + #expect(reader.read(key) == "from-cli") + } + + @Test("Required string: ENV used when CLI absent") + internal func requiredStringENVFallback() { + let reader = MockReader(strings: [envKey: "from-env"]) + #expect(reader.read(key) == "from-env") + } + + @Test("Required string: default used when neither source present") + internal func requiredStringDefault() { + let reader = MockReader() + #expect(reader.read(key) == "default-url") + } + + @Test("Required bool: CLI flag presence is true") + internal func boolCLIPresence() { + let boolKey = ConfigKey("verbose", envPrefix: "BRIGHTDIGIT", default: false) + let reader = MockReader(strings: [boolKey.key(for: .commandLine)!: ""]) + #expect(reader.read(boolKey) == true) + } + + @Test( + "Required bool: ENV truthy strings", + arguments: [("true", true), ("1", true), ("YES", true), ("false", false), ("0", false)] + ) + internal func boolENVParsing(value: String, expected: Bool) { + let boolKey = ConfigKey("verbose", envPrefix: "BRIGHTDIGIT", default: false) + let reader = MockReader(strings: [boolKey.key(for: .environment)!: value]) + #expect(reader.read(boolKey) == expected) + } + + @Test("Required bool: default when absent") + internal func boolDefault() { + let boolKey = ConfigKey("verbose", envPrefix: "BRIGHTDIGIT", default: true) + #expect(MockReader().read(boolKey) == true) + } + + @Test("Optional int: parsed with precedence, nil when absent") + internal func optionalInt() { + let intKey = OptionalConfigKey("episode-number", envPrefix: "BRIGHTDIGIT") + let reader = MockReader(ints: [intKey.key(for: .commandLine)!: 42]) + #expect(reader.read(intKey) == 42) + #expect(MockReader().read(intKey) == nil) + } + + @Test("Optional double: parsed, nil when absent") + internal func optionalDouble() { + let doubleKey = OptionalConfigKey("min-interval", envPrefix: "BRIGHTDIGIT") + let reader = MockReader(doubles: [doubleKey.key(for: .environment)!: 1.5]) + #expect(reader.read(doubleKey) == 1.5) + #expect(MockReader().read(doubleKey) == nil) + } + + @Test("Optional string: nil when absent") + internal func optionalStringNil() { + let optKey = OptionalConfigKey("episode-title", envPrefix: "BRIGHTDIGIT") + #expect(MockReader().read(optKey) == nil) + } + + @Test("Optional date: ISO8601 parsed from value") + internal func optionalDate() { + let dateKey = OptionalConfigKey("published-at", envPrefix: "BRIGHTDIGIT") + let iso = "2026-06-17T00:00:00Z" + let reader = MockReader(strings: [dateKey.key(for: .commandLine)!: iso]) + #expect(reader.read(dateKey) == ISO8601DateFormatter().date(from: iso)) + #expect(MockReader().read(dateKey) == nil) + } +} From 355b7d43c4113a581fdbd493085517701c6a8004 Mon Sep 17 00:00:00 2001 From: Leo Dion Date: Wed, 17 Jun 2026 20:13:48 -0400 Subject: [PATCH 2/3] ConfigKeyKit tests: satisfy STRICT lint (force_unwrap, one_declaration_per_file) Split MockConfigValueReader into its own file and replace force-unwraps with try #require, so swift-format + SwiftLint --strict pass. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../ConfigValueReadingTests.swift | 73 +++++++------------ .../MockConfigValueReader.swift | 59 +++++++++++++++ 2 files changed, 87 insertions(+), 45 deletions(-) create mode 100644 Tests/ConfigKeyKitTests/MockConfigValueReader.swift diff --git a/Tests/ConfigKeyKitTests/ConfigValueReadingTests.swift b/Tests/ConfigKeyKitTests/ConfigValueReadingTests.swift index e671560..1286741 100644 --- a/Tests/ConfigKeyKitTests/ConfigValueReadingTests.swift +++ b/Tests/ConfigKeyKitTests/ConfigValueReadingTests.swift @@ -32,56 +32,35 @@ import Testing @testable import ConfigKeyKit -/// Dict-backed mock reader, keyed by the exact per-source key strings that -/// `ConfigKey`/`OptionalConfigKey` produce. No configuration framework needed. -private struct MockReader: ConfigValueReading { - var strings: [String: String] = [:] - var ints: [String: Int] = [:] - var doubles: [String: Double] = [:] - - func makeConfigKey(_ string: String) -> String { string } - - func string(forKey key: String, isSecret _: Bool, fileID _: String, line _: UInt) -> String? { - strings[key] - } - - func int(forKey key: String, isSecret _: Bool, fileID _: String, line _: UInt) -> Int? { - ints[key] - } - - func double(forKey key: String, isSecret _: Bool, fileID _: String, line _: UInt) -> Double? { - doubles[key] - } -} - @Suite("ConfigValueReading Tests") internal struct ConfigValueReadingTests { private let key = ConfigKey("base-url", envPrefix: "BRIGHTDIGIT", default: "default-url") - private var cliKey: String { key.key(for: .commandLine)! } - private var envKey: String { key.key(for: .environment)! } @Test("Required string: CLI wins over ENV") - internal func requiredStringCLIPrecedence() { - let reader = MockReader(strings: [cliKey: "from-cli", envKey: "from-env"]) + internal func requiredStringCLIPrecedence() throws { + let cli = try #require(key.key(for: .commandLine)) + let env = try #require(key.key(for: .environment)) + let reader = MockConfigValueReader(strings: [cli: "from-cli", env: "from-env"]) #expect(reader.read(key) == "from-cli") } @Test("Required string: ENV used when CLI absent") - internal func requiredStringENVFallback() { - let reader = MockReader(strings: [envKey: "from-env"]) + internal func requiredStringENVFallback() throws { + let env = try #require(key.key(for: .environment)) + let reader = MockConfigValueReader(strings: [env: "from-env"]) #expect(reader.read(key) == "from-env") } @Test("Required string: default used when neither source present") internal func requiredStringDefault() { - let reader = MockReader() - #expect(reader.read(key) == "default-url") + #expect(MockConfigValueReader().read(key) == "default-url") } @Test("Required bool: CLI flag presence is true") - internal func boolCLIPresence() { + internal func boolCLIPresence() throws { let boolKey = ConfigKey("verbose", envPrefix: "BRIGHTDIGIT", default: false) - let reader = MockReader(strings: [boolKey.key(for: .commandLine)!: ""]) + let cli = try #require(boolKey.key(for: .commandLine)) + let reader = MockConfigValueReader(strings: [cli: ""]) #expect(reader.read(boolKey) == true) } @@ -89,46 +68,50 @@ internal struct ConfigValueReadingTests { "Required bool: ENV truthy strings", arguments: [("true", true), ("1", true), ("YES", true), ("false", false), ("0", false)] ) - internal func boolENVParsing(value: String, expected: Bool) { + internal func boolENVParsing(value: String, expected: Bool) throws { let boolKey = ConfigKey("verbose", envPrefix: "BRIGHTDIGIT", default: false) - let reader = MockReader(strings: [boolKey.key(for: .environment)!: value]) + let env = try #require(boolKey.key(for: .environment)) + let reader = MockConfigValueReader(strings: [env: value]) #expect(reader.read(boolKey) == expected) } @Test("Required bool: default when absent") internal func boolDefault() { let boolKey = ConfigKey("verbose", envPrefix: "BRIGHTDIGIT", default: true) - #expect(MockReader().read(boolKey) == true) + #expect(MockConfigValueReader().read(boolKey) == true) } @Test("Optional int: parsed with precedence, nil when absent") - internal func optionalInt() { + internal func optionalInt() throws { let intKey = OptionalConfigKey("episode-number", envPrefix: "BRIGHTDIGIT") - let reader = MockReader(ints: [intKey.key(for: .commandLine)!: 42]) + let cli = try #require(intKey.key(for: .commandLine)) + let reader = MockConfigValueReader(ints: [cli: 42]) #expect(reader.read(intKey) == 42) - #expect(MockReader().read(intKey) == nil) + #expect(MockConfigValueReader().read(intKey) == nil) } @Test("Optional double: parsed, nil when absent") - internal func optionalDouble() { + internal func optionalDouble() throws { let doubleKey = OptionalConfigKey("min-interval", envPrefix: "BRIGHTDIGIT") - let reader = MockReader(doubles: [doubleKey.key(for: .environment)!: 1.5]) + let env = try #require(doubleKey.key(for: .environment)) + let reader = MockConfigValueReader(doubles: [env: 1.5]) #expect(reader.read(doubleKey) == 1.5) - #expect(MockReader().read(doubleKey) == nil) + #expect(MockConfigValueReader().read(doubleKey) == nil) } @Test("Optional string: nil when absent") internal func optionalStringNil() { let optKey = OptionalConfigKey("episode-title", envPrefix: "BRIGHTDIGIT") - #expect(MockReader().read(optKey) == nil) + #expect(MockConfigValueReader().read(optKey) == nil) } @Test("Optional date: ISO8601 parsed from value") - internal func optionalDate() { + internal func optionalDate() throws { let dateKey = OptionalConfigKey("published-at", envPrefix: "BRIGHTDIGIT") let iso = "2026-06-17T00:00:00Z" - let reader = MockReader(strings: [dateKey.key(for: .commandLine)!: iso]) + let cli = try #require(dateKey.key(for: .commandLine)) + let reader = MockConfigValueReader(strings: [cli: iso]) #expect(reader.read(dateKey) == ISO8601DateFormatter().date(from: iso)) - #expect(MockReader().read(dateKey) == nil) + #expect(MockConfigValueReader().read(dateKey) == nil) } } diff --git a/Tests/ConfigKeyKitTests/MockConfigValueReader.swift b/Tests/ConfigKeyKitTests/MockConfigValueReader.swift new file mode 100644 index 0000000..322242b --- /dev/null +++ b/Tests/ConfigKeyKitTests/MockConfigValueReader.swift @@ -0,0 +1,59 @@ +// +// MockConfigValueReader.swift +// ConfigKeyKit +// +// Created by Leo Dion. +// Copyright © 2026 BrightDigit. +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +@testable import ConfigKeyKit + +/// Dict-backed ``ConfigValueReading`` keyed by the exact per-source key strings +/// that `ConfigKey` / `OptionalConfigKey` produce, so the shared `read(_:)` +/// resolution can be exercised without any configuration framework. +internal struct MockConfigValueReader: ConfigValueReading { + internal var strings: [String: String] = [:] + internal var ints: [String: Int] = [:] + internal var doubles: [String: Double] = [:] + + internal func makeConfigKey(_ string: String) -> String { string } + + internal func string( + forKey key: String, isSecret _: Bool, fileID _: String, line _: UInt + ) -> String? { + strings[key] + } + + internal func int( + forKey key: String, isSecret _: Bool, fileID _: String, line _: UInt + ) -> Int? { + ints[key] + } + + internal func double( + forKey key: String, isSecret _: Bool, fileID _: String, line _: UInt + ) -> Double? { + doubles[key] + } +} From f3ebb334967d01ab2e4d3e84ec23f2e5f0294936 Mon Sep 17 00:00:00 2001 From: leogdion Date: Fri, 19 Jun 2026 15:35:54 -0400 Subject: [PATCH 3/3] Add Claude Code GitHub Workflow (#4) * "Update Claude PR Assistant workflow" * "Update Claude Code Review workflow" --- .github/workflows/claude-code-review.yml | 24 +++++++----------------- .github/workflows/claude.yml | 2 +- 2 files changed, 8 insertions(+), 18 deletions(-) diff --git a/.github/workflows/claude-code-review.yml b/.github/workflows/claude-code-review.yml index 9adfd52..b5e8cfd 100644 --- a/.github/workflows/claude-code-review.yml +++ b/.github/workflows/claude-code-review.yml @@ -17,14 +17,14 @@ jobs: # 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 @@ -36,19 +36,9 @@ jobs: uses: anthropics/claude-code-action@v1 with: claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }} - allowed_bots: 'codefactor-io[bot]' - 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. - + plugin_marketplaces: 'https://github.com/anthropics/claude-code.git' + plugins: 'code-review@claude-code-plugins' + prompt: '/code-review:code-review ${{ github.repository }}/pull/${{ github.event.pull_request.number }}' # 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: '--model sonnet --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:*)"' \ No newline at end of file + # or https://code.claude.com/docs/en/cli-reference for available options + diff --git a/.github/workflows/claude.yml b/.github/workflows/claude.yml index d300267..6b15fac 100644 --- a/.github/workflows/claude.yml +++ b/.github/workflows/claude.yml @@ -46,5 +46,5 @@ jobs: # Optional: Add claude_args to customize behavior and configuration # See https://github.com/anthropics/claude-code-action/blob/main/docs/usage.md # or https://code.claude.com/docs/en/cli-reference for available options - # claude_args: '--allowed-tools Bash(gh pr:*)' + # claude_args: '--allowed-tools Bash(gh pr *)'