feat: add Git repository check and automatic .gitignore append safety#257
feat: add Git repository check and automatic .gitignore append safety#257sandman-sh wants to merge 2 commits into
Conversation
WalkthroughGit utilities now detect Git repositories and manage ChangesGitignore safety flow
GitHub Script workflow updates
Estimated code review effort: 3 (Moderate) | ~25 minutes Sequence Diagram(s)sequenceDiagram
participant User
participant Init
participant GitUtils
participant Filesystem
User->>Init: run init
Init->>GitUtils: checkTestspriteIgnored(projectDir)
GitUtils->>Filesystem: read .gitignore
Filesystem-->>GitUtils: ignore-rule content
GitUtils-->>Init: ignored status
Init->>GitUtils: ensureTestspriteIgnored(projectDir)
GitUtils->>Filesystem: update .gitignore when needed
Filesystem-->>GitUtils: write result
GitUtils-->>Init: modification status
Possibly related PRs
Suggested reviewers: 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/lib/git-utils.ts`:
- Around line 45-60: Update the ignore-pattern classification in git-utils.ts
around the existing cleaned, isNegation, and pattern logic so only rules
protecting the complete .testsprite directory are accepted; remove the arbitrary
descendant-prefix checks while preserving negation handling. Update the
corresponding expectations in src/lib/git-utils.test.ts lines 40-43 so specific
child rules such as **/.testsprite/runs return false.
- Around line 100-105: Update the `.gitignore` handling in the
`exists(gitignorePath)` branch to propagate read failures instead of assigning
an empty string in the catch block. Remove the fallback that could overwrite
existing content, allowing the init caller’s existing error handling to emit a
non-blocking warning.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: 18277f47-a9ad-4b59-b42d-0d472eee8512
⛔ Files ignored due to path filters (1)
package-lock.jsonis excluded by!**/package-lock.json,!package-lock.json
📒 Files selected for processing (5)
src/commands/doctor.test.tssrc/commands/doctor.tssrc/commands/init.tssrc/lib/git-utils.test.tssrc/lib/git-utils.ts
| const cleaned = line.replace(/#.*$/, '').trim(); | ||
| if (!cleaned) continue; | ||
|
|
||
| // A negation pattern like `!.testsprite/` un-ignores the directory. | ||
| const isNegation = cleaned.startsWith('!'); | ||
| const pattern = isNegation ? cleaned.slice(1) : cleaned; | ||
|
|
||
| if ( | ||
| pattern === '.testsprite' || | ||
| pattern === '.testsprite/' || | ||
| pattern === '**/.testsprite' || | ||
| pattern === '**/.testsprite/' || | ||
| pattern.startsWith('.testsprite/') || | ||
| pattern.startsWith('**/.testsprite/') | ||
| ) { | ||
| ignored = !isNegation; |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Partial descendant rules are incorrectly classified as complete .testsprite/ protection. This can leave artifacts committable while setup and doctor report the workspace as safe.
src/lib/git-utils.ts#L45-L60: only classify patterns that protect the complete.testspritedirectory; do not accept arbitrary descendant prefixes.src/lib/git-utils.test.ts#L40-L43: expect specific child rules such as**/.testsprite/runsto returnfalse.
As per path instructions, focus on “Correctness and clear error handling.”
📍 Affects 2 files
src/lib/git-utils.ts#L45-L60(this comment)src/lib/git-utils.test.ts#L40-L43
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/lib/git-utils.ts` around lines 45 - 60, Update the ignore-pattern
classification in git-utils.ts around the existing cleaned, isNegation, and
pattern logic so only rules protecting the complete .testsprite directory are
accepted; remove the arbitrary descendant-prefix checks while preserving
negation handling. Update the corresponding expectations in
src/lib/git-utils.test.ts lines 40-43 so specific child rules such as
**/.testsprite/runs return false.
Source: Path instructions
| if (await exists(gitignorePath)) { | ||
| try { | ||
| content = await read(gitignorePath); | ||
| } catch { | ||
| content = ''; | ||
| } |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🔴 Critical | ⚡ Quick win
Preserve .gitignore when reading it fails.
The catch changes existing content to ''; if the subsequent write succeeds, the entire file is replaced with only the TestSprite entry. Propagate the read error—the init caller already converts it into a non-blocking warning.
Proposed fix
if (await exists(gitignorePath)) {
- try {
- content = await read(gitignorePath);
- } catch {
- content = '';
- }
+ content = await read(gitignorePath);
}As per path instructions, focus on “Correctness and clear error handling.”
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| if (await exists(gitignorePath)) { | |
| try { | |
| content = await read(gitignorePath); | |
| } catch { | |
| content = ''; | |
| } | |
| if (await exists(gitignorePath)) { | |
| content = await read(gitignorePath); | |
| } |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/lib/git-utils.ts` around lines 100 - 105, Update the `.gitignore`
handling in the `exists(gitignorePath)` branch to propagate read failures
instead of assigning an empty string in the catch block. Remove the fallback
that could overwrite existing content, allowing the init caller’s existing error
handling to emit a non-blocking warning.
Source: Path instructions
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In @.github/workflows/ci-nudge.yml:
- Line 44: Replace the mutable actions/github-script@v8 reference with the exact
40-character commit SHA for the selected v8 release in
.github/workflows/ci-nudge.yml:44, .github/workflows/issue-triage.yml:52, and
.github/workflows/pr-triage.yml:64.
- Around line 53-55: Both workflows call unavailable getOctokit directly under
actions/github-script@v8. In .github/workflows/ci-nudge.yml lines 53-55 and
.github/workflows/pr-triage.yml lines 78-80, restore access through
require('`@actions/github`').getOctokit(...) or upgrade actions/github-script to
v9+; apply the same fix at both sites.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: fa03111b-68c7-4a07-9af4-dac938346b40
📒 Files selected for processing (3)
.github/workflows/ci-nudge.yml.github/workflows/issue-triage.yml.github/workflows/pr-triage.yml
| private-key: ${{ secrets.TESTSPRITE_HOB_PRIVATE_KEY || secrets.ALFHEIM_AGENT_PRIVATE_KEY }} | ||
|
|
||
| - uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7.0.1 | ||
| - uses: actions/github-script@v8 |
There was a problem hiding this comment.
🔒 Security & Privacy | 🟠 Major | ⚡ Quick win
Pin all external action references immutably.
Each workflow uses the mutable actions/github-script@v8 tag:
.github/workflows/ci-nudge.yml#L44-L44: pinactions/github-scriptto the exact 40-character SHA for the selected v8 release..github/workflows/issue-triage.yml#L52-L52: pinactions/github-scriptto the exact 40-character SHA for the selected v8 release..github/workflows/pr-triage.yml#L64-L64: pinactions/github-scriptto the exact 40-character SHA for the selected v8 release.
📍 Affects 3 files
.github/workflows/ci-nudge.yml#L44-L44(this comment).github/workflows/issue-triage.yml#L52-L52.github/workflows/pr-triage.yml#L64-L64
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In @.github/workflows/ci-nudge.yml at line 44, Replace the mutable
actions/github-script@v8 reference with the exact 40-character commit SHA for
the selected v8 release in .github/workflows/ci-nudge.yml:44,
.github/workflows/issue-triage.yml:52, and .github/workflows/pr-triage.yml:64.
Source: Path instructions
| const appClient = process.env.APP_TOKEN | ||
| ? require('@actions/github').getOctokit(process.env.APP_TOKEN) | ||
| ? getOctokit(process.env.APP_TOKEN) | ||
| : null; |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '\n== Workflow references ==\n'
rg -n "actions/github-script|getOctokit|APP_TOKEN" .github/workflows/ci-nudge.yml .github/workflows/pr-triage.yml
printf '\n== Relevant workflow snippets ==\n'
for f in .github/workflows/ci-nudge.yml .github/workflows/pr-triage.yml; do
echo "--- $f ---"
nl -ba "$f" | sed -n '1,220p' | rg -n "actions/github-script|getOctokit|APP_TOKEN|script:|with:"
done
printf '\n== Upstream github-script v8 source check ==\n'
python3 - <<'PY'
import urllib.request, re
url = "https://raw.githubusercontent.com/actions/github-script/v8.0.0/dist/index.js"
data = urllib.request.urlopen(url, timeout=20).read().decode()
for needle in ["getOctokit", "github-token", "script:", "context", "core"]:
print(f"{needle}: {'yes' if needle in data else 'no'}")
m = re.search(r"getOctokit", data)
print("first getOctokit index:", m.start() if m else None)
PYRepository: TestSprite/testsprite-cli
Length of output: 974
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== Workflow excerpts =="
python3 - <<'PY'
from pathlib import Path
for f in [Path('.github/workflows/ci-nudge.yml'), Path('.github/workflows/pr-triage.yml')]:
print(f"\n--- {f} ---")
lines = f.read_text().splitlines()
for i, line in enumerate(lines, 1):
if 'actions/github-script@v8' in line or 'APP_TOKEN' in line or 'getOctokit' in line:
start = max(1, i-4)
end = min(len(lines), i+4)
for j in range(start, end+1):
print(f"{j:4d}: {lines[j-1]}")
print()
PY
echo "== github-script v8 source: helper injection =="
python3 - <<'PY'
import urllib.request, re, sys
url = "https://raw.githubusercontent.com/actions/github-script/v8.0.0/dist/index.js"
data = urllib.request.urlopen(url, timeout=20).read().decode()
for needle in ["getOctokit", "context", "github-token", "with:", "script"]:
print(f"{needle}: {'found' if needle in data else 'missing'}")
for pat in [r"const\s+getOctokit", r"getOctokit\s*[:=]", r"inputs\.github-token"]:
m = re.search(pat, data)
print(pat, "->", bool(m), m.start() if m else None)
PYRepository: TestSprite/testsprite-cli
Length of output: 6451
🏁 Script executed:
#!/bin/bash
set -euo pipefail
python3 - <<'PY'
import urllib.request, re
url = "https://raw.githubusercontent.com/actions/github-script/v8.0.0/dist/index.js"
data = urllib.request.urlopen(url, timeout=20).read().decode()
# Print short surrounding context around getOctokit occurrences.
for m in re.finditer(r'getOctokit', data):
s = max(0, m.start() - 220)
e = min(len(data), m.end() + 220)
print("..."+data[s:e]+"...\n")
break
PYRepository: TestSprite/testsprite-cli
Length of output: 2757
🏁 Script executed:
#!/bin/bash
set -euo pipefail
python3 - <<'PY'
from pathlib import Path
for f in [Path('.github/workflows/ci-nudge.yml'), Path('.github/workflows/pr-triage.yml')]:
print(f"\n--- {f} ---")
lines = f.read_text().splitlines()
for i, line in enumerate(lines, 1):
if 'actions/github-script@v8' in line or 'APP_TOKEN' in line or 'getOctokit' in line:
start = max(1, i-3)
end = min(len(lines), i+3)
for j in range(start, end+1):
print(f"{j:4d}: {lines[j-1]}")
print()
PY
python3 - <<'PY'
import urllib.request, re
url = "https://raw.githubusercontent.com/actions/github-script/v8.0.0/dist/index.js"
data = urllib.request.urlopen(url, timeout=20).read().decode()
for needle in ["getOctokit", "github-token", "script", "context"]:
print(f"{needle}: {'found' if needle in data else 'missing'}")
m = re.search(r'getOctokit', data)
if m:
s = max(0, m.start()-200)
e = min(len(data), m.end()+300)
print("\ncontext:\n")
print(data[s:e])
PYRepository: TestSprite/testsprite-cli
Length of output: 5416
🌐 Web query:
actions/github-script v8 getOctokit script parameters documentation
💡 Result:
In actions/github-script v8, the getOctokit function is not available as an injected factory function [1][2]. The getOctokit functionality was introduced in v9 [3][4]. In v8, users typically access the GitHub API via the pre-authenticated github client provided in the script context [5]. If you need a client with a different token in v8, you must manually import the Octokit library or the @actions/github package, though users should be aware that the v9 upgrade (which introduced the built-in getOctokit factory) specifically addressed the limitations and breaking changes associated with manual imports in newer versions of the action [3][6][4]. If you require the getOctokit factory function, you should upgrade to actions/github-script v9 or later [3][4]. In v9, getOctokit is available directly in the script context and accepts the following parameters [7][8]: 1. token (string, required): A GitHub token (e.g., PAT, GitHub App token). 2. opts (object, optional): Octokit constructor options (e.g., userAgent, baseUrl, request, retry). Note that in v9, getOctokit is injected as a function parameter, so attempting to declare it with const or let will result in a SyntaxError [8][4].
Citations:
- 1: https://github.com/actions/github-script/releases/tag/v8
- 2: actions/github-script@v7.1.0...v8.0.0
- 3: https://github.com/actions/github-script/releases
- 4: https://github.com/actions/github-script/releases/tag/v9.0.0
- 5: https://github.com/marketplace/actions/github-script
- 6: feat!: add getOctokit to script context, upgrade @actions/github v9, @octokit/core v7, and related packages actions/github-script#700
- 7: https://github.com/actions/github-script
- 8: https://github.com/actions/github-script?tab=readme-ov-file
getOctokit is unavailable in actions/github-script@v8
Both workflows will hit ReferenceError when APP_TOKEN is set:
.github/workflows/ci-nudge.yml.github/workflows/pr-triage.yml
Restore require('@actions/github').getOctokit(...), or upgrade actions/github-script to v9+.
📍 Affects 2 files
.github/workflows/ci-nudge.yml#L53-L55(this comment).github/workflows/pr-triage.yml#L78-L80
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In @.github/workflows/ci-nudge.yml around lines 53 - 55, Both workflows call
unavailable getOctokit directly under actions/github-script@v8. In
.github/workflows/ci-nudge.yml lines 53-55 and .github/workflows/pr-triage.yml
lines 78-80, restore access through require('`@actions/github`').getOctokit(...)
or upgrade actions/github-script to v9+; apply the same fix at both sites.
This Pull Request introduces workspace diagnostics for Git repositories and automatic onboarding safety for
.gitignore.Closes #258
Key Features Implemented:
testsprite doctor): Adds a diagnostic check verifying if the workspace is located inside a Git repository. Warns the user if not, since Git tracking is required for agent skills to operate properly.testsprite doctor): Inspects the.gitignorefile to verify that the.testsprite/folder is ignored to avoid committing large logs or failure artifacts.testsprite setup): Automatically appends.testsprite/to the.gitignorefile during initialization (respecting the--no-agentoption to bypass modification)..gitignoreentries, accounting for comments, whitespace, and negation patterns (e.g.!.testsprite/correctly counts as not ignored).Verification: