standard_id-google 0.1.1 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 77c483758db0802e58687b5c8ed4465ed8d4c5ecb1bd4af9f7078b6ed12d236b
4
- data.tar.gz: '00569352c386b2b28c6cfacbdda6640f2bcaa287af315c2d15820726bc7a89be'
3
+ metadata.gz: 48adde03a7f835aa201d6874ff53cdf949b5cc92a123e42a3481421ff8d3ebb3
4
+ data.tar.gz: 52a7009ce1d2015890d8f56f461e49f307250feca184f80953eecadf13ef16a8
5
5
  SHA512:
6
- metadata.gz: 528bb72eb448ce2f3c4078d64b31d9d91ab6602cd176279ead86226849c295a25494af7cb4a8b0703260f90b47bf5a77d094b5b137eef5506c4e46158052f7d7
7
- data.tar.gz: 4cf122d55787bf6fa36902023afab87c4540c0a9824899447a7b0e81ce4082f9ca3a0f92d0f1d970067c61f7a52ab9638c97a7d7bd2fd3d5cd1d6f44a113c8a6
6
+ metadata.gz: 8865520036b8f389c0acd3d02607370ffa0b19822750e934cb04d2ac3d01cbf2d1c530627e76e01aadf5a1aa72ac5b095d4299d960067599e3196611855d7e30
7
+ data.tar.gz: c326578aecf2141db5e4f4d476242fafcda37699394121f1d7a7e14d1ff4af6cb4ff2f8cd4ff92950baef55cfad270155930f81acfcaeaf4972916a18d990ef8
@@ -0,0 +1,84 @@
1
+ #!/bin/bash
2
+ # Enforce worktree-only file modifications for Claude Code
3
+ #
4
+ # Runs on PreToolUse for Edit, Write, and NotebookEdit tools.
5
+ # Blocks all file modifications in the main checkout — changes must
6
+ # happen inside a git worktree (.worktrees/<name>/).
7
+ #
8
+ # Exit codes:
9
+ # 0 — allow (in a worktree, CI, or non-git path)
10
+ # 2 — block (in main checkout)
11
+ #
12
+ # No programmatic bypass — this is intentional. If the hook itself has a bug,
13
+ # a human must fix it manually (edit the file or remove from settings.json).
14
+ #
15
+ # Note: This hook covers Edit, Write, and NotebookEdit tools. Bash tool writes
16
+ # (echo/sed/tee/cp) are not intercepted — the CLAUDE.md instruction is the
17
+ # enforcement layer for those. Covering Bash reliably would require parsing
18
+ # arbitrary shell commands, which is brittle.
19
+
20
+ # Guard: jq required for JSON parsing
21
+ if ! command -v jq >/dev/null 2>&1; then
22
+ exit 0
23
+ fi
24
+
25
+ # Skip in CI environments
26
+ if [[ "${CI:-}" == "true" ]] || [[ -n "${GITHUB_ACTIONS:-}" ]]; then
27
+ exit 0
28
+ fi
29
+
30
+ INPUT=$(cat)
31
+ TOOL_NAME=$(printf '%s' "$INPUT" | jq -r '.tool_name // ""') || exit 0
32
+
33
+ # Extract file path based on tool (NotebookEdit uses notebook_path, not file_path)
34
+ case "$TOOL_NAME" in
35
+ Edit|Write)
36
+ FILE_PATH=$(printf '%s' "$INPUT" | jq -r '.tool_input.file_path // ""') || exit 0
37
+ ;;
38
+ NotebookEdit)
39
+ FILE_PATH=$(printf '%s' "$INPUT" | jq -r '.tool_input.notebook_path // .tool_input.file_path // ""') || exit 0
40
+ ;;
41
+ *)
42
+ exit 0
43
+ ;;
44
+ esac
45
+
46
+ if [[ -z "$FILE_PATH" ]]; then
47
+ exit 0
48
+ fi
49
+
50
+ # Find an existing directory to run git commands in
51
+ if [[ -d "$FILE_PATH" ]]; then
52
+ CHECK_DIR="$FILE_PATH"
53
+ elif [[ -e "$FILE_PATH" ]]; then
54
+ CHECK_DIR=$(dirname "$FILE_PATH")
55
+ else
56
+ # File doesn't exist yet — walk up to find an existing directory
57
+ CHECK_DIR=$(dirname "$FILE_PATH")
58
+ while [[ ! -d "$CHECK_DIR" ]] && [[ "$CHECK_DIR" != "/" ]]; do
59
+ CHECK_DIR=$(dirname "$CHECK_DIR")
60
+ done
61
+ fi
62
+
63
+ if [[ ! -d "$CHECK_DIR" ]]; then
64
+ exit 0
65
+ fi
66
+
67
+ # Check if we're inside a git repository at all
68
+ GIT_DIR=$(cd "$CHECK_DIR" && git rev-parse --git-dir 2>/dev/null) || exit 0
69
+
70
+ # Make relative paths absolute
71
+ if [[ "$GIT_DIR" != /* ]]; then
72
+ GIT_DIR=$(cd "$CHECK_DIR" && cd "$GIT_DIR" && pwd) || exit 0
73
+ fi
74
+
75
+ # If git-dir contains /worktrees/, we're in a worktree — allow
76
+ if [[ "$GIT_DIR" == *".git/worktrees/"* ]]; then
77
+ exit 0
78
+ fi
79
+
80
+ # We're in the main checkout — block
81
+ echo "❌ Blocked: Cannot modify files in the main checkout." >&2
82
+ echo " Create a worktree first: /worktree or /start <issue>" >&2
83
+ echo " File: $FILE_PATH" >&2
84
+ exit 2
@@ -0,0 +1,24 @@
1
+ {
2
+ "permissions": {
3
+ "allow": [
4
+ "Skill(worktree)",
5
+ "Skill(start)",
6
+ "Bash(git worktree:*)",
7
+ "Bash(git stash:*)"
8
+ ]
9
+ },
10
+ "hooks": {
11
+ "PreToolUse": [
12
+ {
13
+ "matcher": "Edit|Write|NotebookEdit",
14
+ "hooks": [
15
+ {
16
+ "type": "command",
17
+ "command": "$CLAUDE_PROJECT_DIR/.claude/hooks/enforce-worktree.sh",
18
+ "timeout": 10
19
+ }
20
+ ]
21
+ }
22
+ ]
23
+ }
24
+ }
@@ -0,0 +1,211 @@
1
+ ---
2
+ name: publish-gem
3
+ description: "Publish a Ruby gem to RubyGems.org via CI. Use when the user says 'publish gem', 'push gem', 'release gem', or '/publish-gem'. Handles version bump, changelog, build verification, PR, tagging, and CI-driven publish."
4
+ ---
5
+
6
+ # Publish Gem Skill
7
+
8
+ Prepare and publish a Ruby gem to RubyGems.org via GitHub Actions trusted publishing.
9
+
10
+ Publishing is done by CI (not locally) — pushing a version tag triggers the `release.yml` workflow which builds the gem, creates a GitHub Release, and pushes to RubyGems via OIDC. This skill handles everything up to and including the tag push.
11
+
12
+ ## Usage
13
+
14
+ ```
15
+ /publish-gem # Full release flow
16
+ /publish-gem --dry-run # Verify everything, stop before PR creation
17
+ ```
18
+
19
+ ## Workflow
20
+
21
+ ### 1. Verify Context
22
+
23
+ Confirm we're in a gem project:
24
+
25
+ ```bash
26
+ # Check current branch
27
+ git branch --show-current
28
+
29
+ # Must have a .gemspec file
30
+ ls *.gemspec
31
+ ```
32
+
33
+ **Blockers:**
34
+ - No `.gemspec` found — stop, not a gem project
35
+ - Multiple `.gemspec` files found — ask the user which one to build
36
+
37
+ **Branch handling:**
38
+ - If not on `main`, switch automatically: `git checkout main` (publishing from non-main is almost never intentional). If the user explicitly requested publishing from a non-main branch, warn and ask for confirmation before switching.
39
+ - After switching (or if already on `main`), always sync with remote:
40
+ ```bash
41
+ git pull --rebase origin main
42
+ ```
43
+ - If the rebase fails due to conflicts, stop and ask the user to resolve them before proceeding
44
+ - After syncing, verify the working tree is clean (`git status --porcelain`). Warn if dirty — the build may include uncommitted changes.
45
+ - Clean up any stale `.gem` files in the working directory before proceeding:
46
+ ```bash
47
+ rm -f *.gem
48
+ ```
49
+
50
+ ### 2. Extract Gem Metadata
51
+
52
+ Read the gemspec to extract key details:
53
+
54
+ ```bash
55
+ # Get gem name and version
56
+ ruby -e "spec = Gem::Specification.load(Dir['*.gemspec'].first); puts \"#{spec.name} #{spec.version}\""
57
+ ```
58
+
59
+ Also read and display:
60
+ - Current version from the gemspec
61
+ - CHANGELOG.md entry for this version (if exists)
62
+ - `spec.files` count to verify packaging
63
+
64
+ ### 3. Pre-Publish Checks
65
+
66
+ ```bash
67
+ # Check if this version is already published on RubyGems.org
68
+ gem info -r <gem_name> -v <version>
69
+
70
+ # Check if CHANGELOG.md exists when gemspec references it
71
+ ruby -e "spec = Gem::Specification.load(Dir['*.gemspec'].first); puts spec.metadata['changelog_uri']"
72
+ test -f CHANGELOG.md && echo "CHANGELOG.md found" || echo "CHANGELOG.md missing"
73
+ ```
74
+
75
+ **Blockers:**
76
+ - Version already published — ask the user what version to bump to (suggest next patch/minor/major). If the user declines, abort the publish.
77
+
78
+ **Warnings:**
79
+ - Gemspec `changelog_uri` is set but `CHANGELOG.md` does not exist locally — warn the user. This will result in a broken link on RubyGems.org.
80
+ - CHANGELOG.md has no entry for the version being published — warn the user. The `release.yml` workflow will fail if no changelog entry exists for the version.
81
+
82
+ ### 4. Version Bump (if needed)
83
+
84
+ When the current version is already published, or the user requests a bump:
85
+
86
+ 1. Update `lib/<gem_name>/version.rb` with the new version
87
+ 2. Run `bundle install` to sync `Gemfile.lock`
88
+
89
+ **Critical:** Always run `bundle install` after changing the version to keep `Gemfile.lock` in sync. Skipping this causes CI failures.
90
+
91
+ ### 5. Update CHANGELOG.md
92
+
93
+ If the `[Unreleased]` section in CHANGELOG.md has content:
94
+ 1. Rename `[Unreleased]` to `[<version>] - <today's date>` (format: YYYY-MM-DD)
95
+ 2. Add a new empty `[Unreleased]` section above it
96
+
97
+ If `[Unreleased]` is empty, warn the user and ask if they want to proceed without changelog entries for this version.
98
+
99
+ ### 6. Run Tests
100
+
101
+ ```bash
102
+ bundle exec rspec
103
+ ```
104
+
105
+ **Blockers:**
106
+ - Tests fail — stop and ask the user to fix. Do not proceed with a failing test suite.
107
+
108
+ ### 7. Build Verification
109
+
110
+ ```bash
111
+ gem build <name>.gemspec
112
+ ```
113
+
114
+ Verify the `.gem` file was created, show its size, then clean up:
115
+
116
+ ```bash
117
+ rm <name>-<version>.gem
118
+ ```
119
+
120
+ ### 8. Release Summary
121
+
122
+ Present a summary before proceeding:
123
+
124
+ ```
125
+ ## Release Summary
126
+
127
+ Name: <gem_name>
128
+ Version: <version>
129
+ Files: <file count> files in gem
130
+ Registry: https://rubygems.org (published by CI via trusted publisher)
131
+
132
+ Changelog:
133
+ <first few lines of the version's CHANGELOG entry>
134
+
135
+ Next steps:
136
+ 1. Create version bump PR
137
+ 2. Merge PR (manual)
138
+ 3. Tag release → CI publishes to RubyGems + creates GitHub Release
139
+ ```
140
+
141
+ If `--dry-run` was passed, stop here.
142
+
143
+ ### 9. Create Version Bump PR
144
+
145
+ Commit the version bump, changelog, and lockfile on a branch and open a PR:
146
+
147
+ ```bash
148
+ git checkout -b chore/release-v<version>
149
+ git add lib/<gem_name>/version.rb Gemfile.lock CHANGELOG.md
150
+ git commit -m "chore: Release v<version>"
151
+ git push -u origin chore/release-v<version>
152
+ gh pr create --title "chore: Release v<version>" --body "$(cat <<'EOF'
153
+ ## Release v<version>
154
+
155
+ <changelog entry for this version>
156
+
157
+ After merging, Claude will tag `v<version>` which triggers CI to:
158
+ - Create a GitHub Release with changelog notes
159
+ - Build and publish the gem to RubyGems.org via trusted publisher
160
+ EOF
161
+ )"
162
+ ```
163
+
164
+ Tell the user to review and merge the PR, then let you know when it's merged.
165
+
166
+ ### 10. Tag the Release (after PR merge)
167
+
168
+ When the user confirms the PR is merged:
169
+
170
+ ```bash
171
+ # Sync main — use reset to avoid divergent branch issues from squash merge
172
+ git checkout main
173
+ git fetch origin main
174
+ git reset --hard origin/main
175
+
176
+ # Tag the merged commit
177
+ git tag -a v<version> -m "Release v<version>"
178
+ git push origin v<version>
179
+
180
+ # Clean up the bump branch locally
181
+ # -D needed because squash merge doesn't preserve individual commits in main's history
182
+ git branch -D chore/release-v<version>
183
+ # Remote branch may already be deleted by GitHub's auto-delete setting — ignore errors
184
+ git push origin --delete chore/release-v<version> 2>/dev/null || true
185
+ ```
186
+
187
+ The tag push triggers `release.yml` which:
188
+ 1. Verifies the tag version matches `version.rb`
189
+ 2. Waits for CI to pass
190
+ 3. Extracts changelog notes and creates a GitHub Release
191
+ 4. Builds the gem and publishes to RubyGems.org via OIDC trusted publisher
192
+
193
+ ### 11. Output
194
+
195
+ Report:
196
+ 1. RubyGems URL: `https://rubygems.org/gems/<name>` (available after CI completes)
197
+ 2. Git tag created: `v<version>`
198
+ 3. GitHub Release + RubyGems publish: triggered by CI — link: `https://github.com/<owner>/<repo>/actions`
199
+ 4. Remind: allow a few minutes for CI to complete, then the gem will appear on RubyGems and the GitHub Release will be created
200
+
201
+ ## Error Handling
202
+
203
+ | Error | Solution |
204
+ |-------|----------|
205
+ | `gem build` fails | Fix gemspec errors and retry |
206
+ | Tests fail | Fix tests before proceeding |
207
+ | Tag already exists | Version was previously tagged — skip tagging |
208
+ | Tag push fails | Likely a permissions issue — report and continue |
209
+ | Remote branch already deleted | GitHub auto-deleted it — ignore the error |
210
+ | User declines version bump | Abort the publish |
211
+ | CI publish fails | Check the Actions tab — common causes: trusted publisher not configured on RubyGems.org, `rubygems` environment not set up in GitHub repo settings, or version mismatch between tag and version.rb |
@@ -0,0 +1,185 @@
1
+ ---
2
+ name: start
3
+ description: "Start working on Linear issues. Use when the user says 'start working on', 'pick up issue', 'work on RAR-123', or wants to begin development on Linear issues. Handles status updates, branch creation, and context gathering."
4
+ ---
5
+
6
+ # Start Skill
7
+
8
+ Begin working on Linear issues with proper setup: update status, create branches, gather context, and track progress.
9
+
10
+ ## Prerequisites
11
+
12
+ This skill requires the Linear MCP server to be configured. If Linear tools (`mcp__linear__*`) are not available, the skill will warn and offer to proceed with git-only setup (branch creation without status updates).
13
+
14
+ ## Scope
15
+
16
+ This skill sets up local development for Linear issues. It does **NOT**:
17
+ - Merge PRs to main (merging is a human decision)
18
+ - Delete branches or worktrees automatically
19
+ - Close or complete Linear issues
20
+
21
+ ## Usage
22
+
23
+ ```
24
+ /start <issue-identifiers...> # Start specific issues (e.g., /start RAR-123 RAR-124)
25
+ /start --mine # Show my assigned issues ready to start
26
+ /start --backlog # Show backlog issues for a team
27
+ ```
28
+
29
+ ## Workflow
30
+
31
+ > **Note:** MCP tool calls shown below use pseudocode syntax for readability.
32
+ > Actual invocation uses Claude's tool use API with the `mcp__linear__*` tools.
33
+
34
+ ### 1. Parse Input and Fetch Issues
35
+
36
+ **If specific identifiers provided:**
37
+
38
+ Fetch each issue using Linear MCP:
39
+
40
+ ```
41
+ mcp__linear__get_issue(id: "RAR-123", includeRelations: true)
42
+ ```
43
+
44
+ **If `--mine` flag:**
45
+
46
+ ```
47
+ mcp__linear__list_issues(assignee: "me", state: "Todo", limit: 10)
48
+ ```
49
+
50
+ **If `--backlog` flag (with optional `--team` and `--project` filters):**
51
+
52
+ ```
53
+ mcp__linear__list_issues(
54
+ team: "<from --team flag, default: Rarebit>",
55
+ project: "<from --project flag, if provided>",
56
+ state: "Backlog",
57
+ limit: 10
58
+ )
59
+ ```
60
+
61
+ Present the issues and let the user select which to work on.
62
+
63
+ ### 2. Pre-Work Checks
64
+
65
+ Before starting, verify:
66
+
67
+ **Check for blockers:**
68
+
69
+ ```
70
+ # From get_issue with includeRelations: true
71
+ # Look at the blocking/blockedBy relations
72
+ ```
73
+
74
+ If blocked:
75
+ ```
76
+ Warning: RAR-123 is blocked by:
77
+ - RAR-120: "Set up middleware" (In Progress)
78
+
79
+ Options:
80
+ 1. Start anyway (work may be blocked)
81
+ 2. Start the blocking issue instead
82
+ 3. Cancel
83
+ ```
84
+
85
+ **Check issue readiness:**
86
+ - Has description/acceptance criteria?
87
+ - Has assigned estimate?
88
+
89
+ If missing context, warn but allow proceeding.
90
+
91
+ ### 3. Update Issue Status
92
+
93
+ **Skip this step if `--no-status` flag is provided.**
94
+
95
+ Update each issue to "In Progress":
96
+
97
+ ```
98
+ mcp__linear__save_issue(
99
+ id: "<issue-uuid>",
100
+ stateId: "<in-progress-state-id>"
101
+ )
102
+ ```
103
+
104
+ The workflow should not block on Linear failures — local development can proceed.
105
+
106
+ ### 4. Set Up Worktree
107
+
108
+ **Always create a worktree** to isolate this work from any other state in the repo. This prevents changes from different sessions bleeding into unrelated PRs.
109
+
110
+ ```bash
111
+ DEFAULT_BRANCH=$(git symbolic-ref refs/remotes/origin/HEAD 2>/dev/null | sed 's@refs/remotes/origin/@@')
112
+ DEFAULT_BRANCH=${DEFAULT_BRANCH:-main}
113
+ git fetch origin "$DEFAULT_BRANCH"
114
+ git worktree add .worktrees/<identifier> -b <branch-name> "origin/$DEFAULT_BRANCH"
115
+ ```
116
+
117
+ **`--no-worktree` flag:** If the user explicitly passes `--no-worktree`, check the current state:
118
+ - On the default branch with a clean working tree → fall back to a simple branch:
119
+ ```bash
120
+ DEFAULT_BRANCH=$(git symbolic-ref refs/remotes/origin/HEAD 2>/dev/null | sed 's@refs/remotes/origin/@@')
121
+ DEFAULT_BRANCH=${DEFAULT_BRANCH:-main}
122
+ git fetch origin "$DEFAULT_BRANCH"
123
+ git checkout -b <branch-name> "origin/$DEFAULT_BRANCH"
124
+ ```
125
+ - Otherwise → **stop and report why**:
126
+ _"Cannot skip worktree: working tree has uncommitted changes (or is on a feature branch). Stash or commit your changes first, switch to the default branch, then re-run with `--no-worktree`."_
127
+
128
+ > **Note:** The previous version of this skill offered stash and branch-switch workflows. Those paths have been removed in favor of always using worktrees. If you prefer to stash instead, run `git stash push -m "WIP"` manually before `/start`.
129
+
130
+ See `/worktree` skill for full worktree conventions.
131
+
132
+ **Branch name format:**
133
+
134
+ Use Linear's `gitBranchName` field if available, or generate:
135
+ `{identifier}/{short-description}` (e.g., `rar-123/add-feature-name`)
136
+
137
+ **Worktree naming:** `.worktrees/<identifier>` (e.g., `.worktrees/rar-123`)
138
+
139
+ ### 5. Display Issue Context
140
+
141
+ ```
142
+ Starting: RAR-123
143
+ Issue: <title>
144
+ URL: https://linear.app/...
145
+
146
+ Description:
147
+ <full description>
148
+
149
+ Acceptance Criteria:
150
+ - [ ] ...
151
+
152
+ Branch: <branch-name>
153
+ ```
154
+
155
+ ### 6. Create Initial Todo List
156
+
157
+ Based on the issue description, create a todo list to track progress.
158
+
159
+ ## Flags Reference
160
+
161
+ | Flag | Description |
162
+ |------|-------------|
163
+ | `--mine` | List my assigned issues in Todo state |
164
+ | `--backlog` | List team backlog issues |
165
+ | `--no-worktree` | Skip worktree if on the default branch + clean; stops with error otherwise |
166
+ | `--no-status` | Skip status update (just create branch) |
167
+ | `--team <name>` | Filter by team (default: Rarebit) |
168
+ | `--project <name>` | Filter by project |
169
+
170
+ ## Error Handling
171
+
172
+ | Error | Solution |
173
+ |-------|----------|
174
+ | Linear MCP unavailable | Warn and offer to proceed with just git setup |
175
+ | Issue not found | Verify identifier, check team access |
176
+ | Issue already in progress | Ask if user wants to continue anyway |
177
+ | Issue is done/canceled | Warn and suggest reopening or selecting different issue |
178
+ | Status update fails | Offer to continue with local setup, retry, or cancel |
179
+ | Branch already exists | Offer to checkout existing or create with suffix |
180
+ | Worktree already exists | Offer to use existing worktree or create with suffix |
181
+
182
+ ## Integration with Other Skills
183
+
184
+ - After completing work, create a PR with `gh pr create`, or use `/publish-gem` when ready to release
185
+ - The branch naming convention ensures the Linear issue can be auto-detected from the branch
@@ -0,0 +1,143 @@
1
+ ---
2
+ name: worktree
3
+ description: "Create an isolated worktree for new work. Always creates a worktree by default to prevent cross-contamination between sessions. Use at the start of any session, or when the user says 'worktree', 'isolate', 'fresh start', or 'new worktree'."
4
+ ---
5
+
6
+ # Worktree Skill
7
+
8
+ Creates an isolated worktree for new work. **Always creates a worktree by default** — this prevents changes from different work sessions bleeding into unrelated PRs.
9
+
10
+ ## Why Worktree-by-Default
11
+
12
+ Every new piece of work gets its own worktree. This eliminates the most common source of cross-contamination: starting new work in a repo that has leftover state from a previous session (uncommitted changes, wrong branch, etc.). The only exception is when the user explicitly opts out.
13
+
14
+ > **Note:** The previous version of this skill offered a stash-based workflow. That path has been removed in favor of always using worktrees. If you prefer to stash instead, run `git stash push -m "WIP"` manually before invoking `/start` or `/worktree`.
15
+
16
+ ## Why `.worktrees/`
17
+
18
+ Worktrees are created inside the repo at `.worktrees/<name>` (not under `.claude/worktrees/`). This ensures:
19
+ - Worktrees are accessible inside devcontainers (mounted at `/workspace/.worktrees/`)
20
+ - Consistent convention across all repos in the workspace
21
+ - The `.worktrees/` directory is gitignored in each repo
22
+
23
+ ## Usage
24
+
25
+ ```
26
+ /worktree # Create a worktree (always, regardless of state)
27
+ /worktree <name> # Create a named worktree directly
28
+ /worktree --stay # Skip worktree creation, work in current checkout
29
+ ```
30
+
31
+ ## Workflow
32
+
33
+ ### Phase 1: Detect Current State
34
+
35
+ Gather the repo state (for reporting, not for deciding whether to create a worktree):
36
+
37
+ ```bash
38
+ # Current branch
39
+ CURRENT_BRANCH=$(git branch --show-current)
40
+
41
+ # Default branch
42
+ DEFAULT_BRANCH=$(git symbolic-ref refs/remotes/origin/HEAD 2>/dev/null | sed 's@refs/remotes/origin/@@')
43
+ DEFAULT_BRANCH=${DEFAULT_BRANCH:-main}
44
+
45
+ # Uncommitted changes (staged + unstaged + untracked)
46
+ git status --porcelain
47
+ ```
48
+
49
+ ### Phase 2: Decision
50
+
51
+ **Default behavior:** Always create a worktree. Go straight to Phase 3 → Phase 4. Any uncommitted changes on the current branch are left untouched — the worktree is a separate checkout, so existing work is preserved exactly as-is.
52
+
53
+ **`--stay` flag:** Skip worktree creation and stay in the current checkout. This is for when the user explicitly wants to continue work on the current branch (e.g., resuming a previous session). If dirty state is detected, inform the user what's there but **do not stop** — the user is consciously choosing to stay. This differs from `/start --no-worktree`, which hard-stops on dirty state because starting new work on an unclean tree risks cross-contamination.
54
+
55
+ ### Phase 3: Fetch Latest
56
+
57
+ Always fetch the latest default branch, regardless of whether a worktree will be created:
58
+
59
+ ```bash
60
+ git fetch origin "$DEFAULT_BRANCH"
61
+ ```
62
+
63
+ This ensures any new branch or worktree starts from the latest codebase.
64
+
65
+ ### Phase 4: Create Worktree
66
+
67
+ **Naming convention:**
68
+
69
+ If a name is provided (e.g., from a Linear issue identifier):
70
+ ```bash
71
+ git worktree add .worktrees/<name> -b <branch-name> "origin/$DEFAULT_BRANCH"
72
+ ```
73
+
74
+ If no name is provided, generate one from context:
75
+ - If starting a Linear issue: use the issue identifier (e.g., `swe-123`)
76
+ - If the user described the task: use a short slug (e.g., `fix-auth-timeout`)
77
+ - Fallback: use today's date in `YYYY-MM-DD` format
78
+
79
+ **Ensure `.worktrees/` is gitignored:**
80
+
81
+ ```bash
82
+ # Check if .worktrees is already in .gitignore
83
+ if ! grep -q '\.worktrees' .gitignore 2>/dev/null; then
84
+ echo '.worktrees/' >> .gitignore
85
+ fi
86
+ ```
87
+
88
+ ### Phase 5: Report
89
+
90
+ After setup, display:
91
+
92
+ ```
93
+ Worktree created at: .worktrees/<name>
94
+ Branch: <branch-name>
95
+ Based on: origin/$DEFAULT_BRANCH (fetched latest)
96
+
97
+ Previous state preserved:
98
+ Branch: <original-branch>
99
+ Changes: <summary of uncommitted changes left behind, if any>
100
+
101
+ Working directory: .worktrees/<name>
102
+ ```
103
+
104
+ If `--stay` was used:
105
+
106
+ ```
107
+ Staying in current checkout: <branch>
108
+ (fetched latest origin/$DEFAULT_BRANCH)
109
+ <summary of uncommitted changes, if any>
110
+
111
+ To create a new branch:
112
+ git checkout -b <branch-name> origin/$DEFAULT_BRANCH
113
+ ```
114
+
115
+ ## Integration with /start
116
+
117
+ The `/start` skill always creates a worktree as part of its workflow. Both skills follow the same conventions:
118
+ - Worktrees go in `.worktrees/`
119
+ - Always fetch latest before creating
120
+ - Naming follows the `<identifier>` convention (e.g., `.worktrees/swe-123`)
121
+
122
+ **Opt-out flags differ by context:**
123
+ - `/worktree --stay` — "stay in current checkout" (you invoked the worktree tool, you're opting out of its action; dirty state is allowed)
124
+ - `/start --no-worktree` — "don't create a worktree" (you're starting new work, skipping one aspect; dirty state causes a hard stop)
125
+
126
+ This `/worktree` skill can also be invoked independently when you want to isolate work without picking up a Linear issue.
127
+
128
+ ## Error Handling
129
+
130
+ | Error | Solution |
131
+ |-------|----------|
132
+ | `.worktrees/<name>` already exists | Offer to reuse existing or create with suffix |
133
+ | Branch name already exists | Check out existing branch in the worktree instead of `-b` |
134
+ | No origin remote | Create worktree from local default branch HEAD |
135
+ | Fetch fails | Warn and create from local default branch HEAD |
136
+ | `.gitignore` is read-only | Warn the user to add `.worktrees/` manually |
137
+
138
+ ## Safety Rules
139
+
140
+ 1. **Never discard uncommitted changes** - the whole point is to preserve them
141
+ 2. **Never force-checkout** over dirty state - always worktree instead
142
+ 3. **Always fetch before creating** - worktrees should start from latest
143
+ 4. **This skill only creates worktrees** - it does not remove or clean up stale ones
data/.ruby-version CHANGED
@@ -1 +1 @@
1
- 3.4.4
1
+ 4.0.1
data/CHANGELOG.md CHANGED
@@ -1,5 +1,32 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
1
8
  ## [Unreleased]
2
9
 
10
+ ## [0.2.0] - 2026-04-21
11
+
12
+ ### Added
13
+
14
+ - Auto-register provider with StandardId via `Rails::Railtie` on `config.after_initialize`, so apps that bundle the gem no longer need an explicit initializer (#27)
15
+
16
+ ## [0.1.2] - 2026-01-13
17
+
18
+ ### Added
19
+
20
+ - Support nonce and passing custom parameters to Google Sign In (#1)
21
+
22
+ ## [0.1.1] - 2025-12-24
23
+
24
+ ### Fixed
25
+
26
+ - Thread safety improvements
27
+
3
28
  ## [0.1.0] - 2025-12-20
4
29
 
5
- - Initial release
30
+ ### Added
31
+
32
+ - Initial release of Google Sign In provider plugin for StandardId
data/CLAUDE.md ADDED
@@ -0,0 +1,26 @@
1
+ # CLAUDE.md
2
+
3
+ ## Worktree-Only Workflow (Enforced)
4
+
5
+ **All file modifications are blocked in the main checkout.** A PreToolUse hook (`enforce-worktree.sh`) rejects Edit, Write, and NotebookEdit operations targeting files outside a worktree. There are no opt-outs. Do not use Bash to write files in the main checkout either (e.g., `echo >`, `sed -i`, `tee`, `cp`) — the hook cannot intercept shell commands, so this rule is instruction-enforced.
6
+
7
+ Before writing any code, create a worktree:
8
+
9
+ ```bash
10
+ DEFAULT_BRANCH=$(git symbolic-ref refs/remotes/origin/HEAD 2>/dev/null | sed 's@refs/remotes/origin/@@')
11
+ DEFAULT_BRANCH=${DEFAULT_BRANCH:-main}
12
+ git fetch origin "$DEFAULT_BRANCH"
13
+ git worktree add .worktrees/<name> -b <branch-name> "origin/$DEFAULT_BRANCH"
14
+ ```
15
+
16
+ Then work inside `.worktrees/<name>/` for the rest of the session.
17
+
18
+ **Naming:** Use the Linear issue identifier if available (e.g., `.worktrees/<identifier>`), a task slug (e.g., `.worktrees/fix-auth-timeout`), or today's date (e.g., `.worktrees/2026-04-01`) as fallback.
19
+
20
+ **The hook allows modifications only when:**
21
+
22
+ 1. The file is inside a git worktree (detected via `git rev-parse --git-dir` returning a path under `.git/worktrees/`)
23
+ 2. Running in a CI/automated context where the checkout is already isolated
24
+ **Why this matters:** Working directly on the main checkout causes cross-contamination between sessions — uncommitted changes, wrong branches, and dirty state leak into unrelated work. Worktrees eliminate this entirely.
25
+
26
+ See the `/worktree` and `/start` skills for full conventions and flags.
@@ -9,33 +9,38 @@ module StandardId
9
9
  USERINFO_ENDPOINT = "https://www.googleapis.com/oauth2/v2/userinfo".freeze
10
10
  TOKEN_INFO_ENDPOINT = "https://oauth2.googleapis.com/tokeninfo".freeze
11
11
  DEFAULT_SCOPE = "openid email profile".freeze
12
+ AUTHORIZATION_PARAM_DEFAULTS = {
13
+ scope: DEFAULT_SCOPE
14
+ }.freeze
12
15
 
13
16
  class << self
14
17
  def provider_name
15
18
  "google"
16
19
  end
17
20
 
18
- def authorization_url(state:, redirect_uri:, **options)
19
- scope = options[:scope] || DEFAULT_SCOPE
20
- prompt = options[:prompt]
21
+ def supported_authorization_params
22
+ [:nonce, :login_hint, :prompt, :scope, :access_type, :hd, :response_mode, :include_granted_scopes]
23
+ end
21
24
 
25
+ def authorization_url(state:, redirect_uri:, **options)
22
26
  query = {
23
27
  client_id: credentials[:client_id],
24
28
  redirect_uri: redirect_uri,
25
29
  response_type: "code",
26
- scope: scope,
27
30
  state: state
28
31
  }
29
32
 
30
- query[:prompt] = prompt if prompt.present?
33
+ supported_authorization_params.each do |param|
34
+ query[param] = options[param] || AUTHORIZATION_PARAM_DEFAULTS[param]
35
+ end
31
36
 
32
- "#{AUTH_ENDPOINT}?#{URI.encode_www_form(query)}"
37
+ "#{AUTH_ENDPOINT}?#{URI.encode_www_form(query.compact)}"
33
38
  end
34
39
 
35
- def get_user_info(code: nil, id_token: nil, access_token: nil, redirect_uri: nil, **_options)
40
+ def get_user_info(code: nil, id_token: nil, access_token: nil, redirect_uri: nil, nonce: nil, **_options)
36
41
  if id_token.present?
37
42
  build_response(
38
- verify_id_token(id_token: id_token),
43
+ verify_id_token(id_token: id_token, nonce: nonce),
39
44
  tokens: { id_token: id_token }
40
45
  )
41
46
  elsif access_token.present?
@@ -44,7 +49,7 @@ module StandardId
44
49
  tokens: { access_token: access_token }
45
50
  )
46
51
  elsif code.present?
47
- exchange_code_for_user_info(code: code, redirect_uri: redirect_uri)
52
+ exchange_code_for_user_info(code: code, redirect_uri: redirect_uri, nonce: nonce)
48
53
  else
49
54
  raise StandardId::InvalidRequestError, "Either code, id_token, or access_token must be provided"
50
55
  end
@@ -61,7 +66,7 @@ module StandardId
61
66
  DEFAULT_SCOPE
62
67
  end
63
68
 
64
- def exchange_code_for_user_info(code:, redirect_uri:)
69
+ def exchange_code_for_user_info(code:, redirect_uri:, nonce: nil)
65
70
  raise StandardId::InvalidRequestError, "Missing authorization code" if code.blank?
66
71
 
67
72
  token_response = HttpClient.post_form(TOKEN_ENDPOINT, {
@@ -80,6 +85,11 @@ module StandardId
80
85
  access_token = parsed_token["access_token"]
81
86
  raise StandardId::InvalidRequestError, "Google response missing access token" if access_token.blank?
82
87
 
88
+ # If we have an ID token in the response and a nonce was provided, verify it
89
+ if parsed_token["id_token"].present? && nonce.present?
90
+ verify_id_token(id_token: parsed_token["id_token"], nonce: nonce)
91
+ end
92
+
83
93
  tokens = extract_token_payload(parsed_token)
84
94
  user_info = fetch_user_info(access_token: access_token)
85
95
 
@@ -90,7 +100,7 @@ module StandardId
90
100
  raise StandardId::OAuthError, e.message, cause: e
91
101
  end
92
102
 
93
- def verify_id_token(id_token:)
103
+ def verify_id_token(id_token:, nonce: nil)
94
104
  raise StandardId::InvalidRequestError, "Missing id_token" if id_token.blank?
95
105
 
96
106
  response = HttpClient.post_form(TOKEN_INFO_ENDPOINT, id_token: id_token)
@@ -99,6 +109,15 @@ module StandardId
99
109
 
100
110
  token_info = JSON.parse(response.body)
101
111
 
112
+ # Validate nonce if provided (web flow with server-generated nonce)
113
+ if nonce.present?
114
+ token_nonce = token_info["nonce"]
115
+ if token_nonce != nonce
116
+ raise StandardId::InvalidRequestError,
117
+ "ID token nonce mismatch. Expected: #{nonce}, got: #{token_nonce}"
118
+ end
119
+ end
120
+
102
121
  unless token_info["aud"] == credentials[:client_id]
103
122
  raise StandardId::InvalidRequestError,
104
123
  "ID token audience mismatch. Expected: #{credentials[:client_id]}, got: #{token_info["aud"]}"
@@ -186,5 +205,3 @@ module StandardId
186
205
  end
187
206
  end
188
207
  end
189
-
190
- StandardId::ProviderRegistry.register(:google, StandardId::Providers::Google)
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module StandardId
4
+ module Google
5
+ class Railtie < ::Rails::Railtie
6
+ config.after_initialize do
7
+ StandardId::ProviderRegistry.register(:google, StandardId::Providers::Google)
8
+
9
+ Rails.logger.debug("[StandardId::Google] registered provider") if Rails.logger
10
+ end
11
+ end
12
+ end
13
+ end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module StandardId
4
4
  module Google
5
- VERSION = "0.1.1"
5
+ VERSION = "0.2.0"
6
6
  end
7
7
  end
@@ -2,3 +2,4 @@ require "active_support/core_ext/numeric/time"
2
2
  require "active_support/core_ext/hash/indifferent_access"
3
3
  require "standard_id"
4
4
  require "standard_id/google/providers/google"
5
+ require "standard_id/google/railtie" if defined?(Rails)
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: standard_id-google
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jaryl Sim
@@ -43,32 +43,40 @@ dependencies:
43
43
  - - ">="
44
44
  - !ruby/object:Gem::Version
45
45
  version: 0.1.7
46
- description: Extracted StandardId::Providers::Apple implementation packaged as a standalone
47
- gem so StandardId installations can opt into Sign in with Apple independently.
46
+ description: Extracted StandardId::Providers::Google implementation packaged as a
47
+ standalone gem so StandardId installations can opt into Sign in with Google independently.
48
48
  email:
49
49
  - code@jaryl.dev
50
50
  executables: []
51
51
  extensions: []
52
52
  extra_rdoc_files: []
53
53
  files:
54
+ - ".claude/hooks/enforce-worktree.sh"
55
+ - ".claude/settings.json"
56
+ - ".claude/skills/publish-gem/SKILL.md"
57
+ - ".claude/skills/start/SKILL.md"
58
+ - ".claude/skills/worktree/SKILL.md"
54
59
  - ".rspec"
55
60
  - ".rubocop.yml"
56
61
  - ".ruby-version"
57
62
  - CHANGELOG.md
63
+ - CLAUDE.md
58
64
  - CODE_OF_CONDUCT.md
59
65
  - LICENSE.txt
60
66
  - README.md
61
67
  - Rakefile
62
68
  - lib/standard_id/google.rb
63
69
  - lib/standard_id/google/providers/google.rb
70
+ - lib/standard_id/google/railtie.rb
64
71
  - lib/standard_id/google/version.rb
65
- homepage: https://github.com/rarebit-one/standard_id_apple
72
+ homepage: https://github.com/rarebit-one/standard_id_google
66
73
  licenses:
67
74
  - MIT
68
75
  metadata:
69
- homepage_uri: https://github.com/rarebit-one/standard_id_apple
70
- source_code_uri: https://github.com/rarebit-one/standard_id_apple
71
- changelog_uri: https://github.com/rarebit-one/standard_id_apple/blob/main/CHANGELOG.md
76
+ homepage_uri: https://github.com/rarebit-one/standard_id_google
77
+ source_code_uri: https://github.com/rarebit-one/standard_id_google
78
+ changelog_uri: https://github.com/rarebit-one/standard_id_google/blob/main/CHANGELOG.md
79
+ bug_tracker_uri: https://github.com/rarebit-one/standard_id_google/issues
72
80
  rdoc_options: []
73
81
  require_paths:
74
82
  - lib
@@ -83,7 +91,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
83
91
  - !ruby/object:Gem::Version
84
92
  version: '0'
85
93
  requirements: []
86
- rubygems_version: 3.6.7
94
+ rubygems_version: 4.0.3
87
95
  specification_version: 4
88
96
  summary: Google Sign In provider plugin for the StandardId engine.
89
97
  test_files: []