gv-ai 1.0.0 → 1.1.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.
- package/.gv-ai/cron.log +25 -0
- package/.gv-ai/env.json +5 -0
- package/.gv-ai/opencode.json +58 -0
- package/.gv-ai/prompts/fetcher.md +42 -0
- package/.gv-ai/prompts/fixer.md +43 -0
- package/.gv-ai/prompts/rca.md +47 -0
- package/.gv-ai/runs/20260118-075502/fetcher.log +1 -0
- package/.gv-ai/scripts/orchestrator.sh +115 -0
- package/.gv-ai/tool/notify.ts +51 -0
- package/dist/cli.js +335 -0
- package/dist/commands/sentry-resolver.d.ts +4 -0
- package/dist/commands/sentry-resolver.d.ts.map +1 -0
- package/dist/commands/show-template.d.ts +2 -0
- package/dist/commands/show-template.d.ts.map +1 -0
- package/dist/templates/agents-md.ts +91 -0
- package/dist/templates/sentry-resolver/prompts/fetcher.md +42 -0
- package/dist/templates/sentry-resolver/prompts/fixer.md +43 -0
- package/dist/templates/sentry-resolver/prompts/rca.md +47 -0
- package/dist/templates/sentry-resolver/scripts/orchestrator.sh +115 -0
- package/dist/templates/sentry-resolver/tool/notify.ts.template +51 -0
- package/package.json +3 -3
package/.gv-ai/cron.log
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
Error: Oh My Zsh can't be loaded from: . You need to run zsh instead.
|
|
2
|
+
Here's the process tree:
|
|
3
|
+
|
|
4
|
+
PPID PID COMMAND
|
|
5
|
+
1 520 /usr/sbin/cron
|
|
6
|
+
520 60485 /usr/sbin/cron
|
|
7
|
+
60485 60486 /bin/sh -c /Users/souvikmahapatra/projects/gv-ai/.gv-ai/scripts/orchestrator.sh >> /Users/souvikmahapatra/projects/gv-ai/.gv-ai/cron.log 2>&1
|
|
8
|
+
60486 60487 /bin/bash /Users/souvikmahapatra/projects/gv-ai/.gv-ai/scripts/orchestrator.sh
|
|
9
|
+
|
|
10
|
+
/Users/souvikmahapatra/.zshrc: line 107: pyenv: command not found
|
|
11
|
+
/Users/souvikmahapatra/.zshrc: line 112: pyenv: command not found
|
|
12
|
+
/Users/souvikmahapatra/.zshrc: line 113: pyenv: command not found
|
|
13
|
+
/Users/souvikmahapatra/.zshrc: line 123: autoload: command not found
|
|
14
|
+
/Users/souvikmahapatra/.bun/_bun: line 922: syntax error near unexpected token `('
|
|
15
|
+
/Users/souvikmahapatra/.bun/_bun: line 922: ` local -a packages_full_path=(${global_node_modules}/*(N))'
|
|
16
|
+
/Users/souvikmahapatra/.bun/_bun: line 924: _alternative: command not found
|
|
17
|
+
/Users/souvikmahapatra/.bun/_bun: line 925: syntax error near unexpected token `}'
|
|
18
|
+
/Users/souvikmahapatra/.bun/_bun: line 925: `}'
|
|
19
|
+
/Users/souvikmahapatra/projects/gv-ai/.gv-ai/scripts/orchestrator.sh: line 35: export: ` SENTRY_PROJECTS: tal ai prod tal ai stag=': not a valid identifier
|
|
20
|
+
/Users/souvikmahapatra/projects/gv-ai/.gv-ai/scripts/orchestrator.sh: line 35: export: ` TELEGRAM_BOT_TOKEN: 7895808491:AAHWA31cob6g0oq1YPdLc2KjCXgFX4y6by0=': not a valid identifier
|
|
21
|
+
/Users/souvikmahapatra/projects/gv-ai/.gv-ai/scripts/orchestrator.sh: line 35: export: ` TELEGRAM_CHAT_ID: 1827747473=': not a valid identifier
|
|
22
|
+
🚀 Starting Sentry Resolver Run: 20260118-075502
|
|
23
|
+
📂 Project Root: /Users/souvikmahapatra/projects/gv-ai
|
|
24
|
+
🔍 Fetching issues...
|
|
25
|
+
⚠️ No issues.json created. Check /Users/souvikmahapatra/projects/gv-ai/.gv-ai/runs/20260118-075502/fetcher.log
|
package/.gv-ai/env.json
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://opencode.ai/config.json",
|
|
3
|
+
"mcp": {
|
|
4
|
+
"sentry": {
|
|
5
|
+
"type": "remote",
|
|
6
|
+
"url": "https://mcp.sentry.dev/mcp",
|
|
7
|
+
"oauth": {}
|
|
8
|
+
},
|
|
9
|
+
"linear": {
|
|
10
|
+
"type": "local",
|
|
11
|
+
"command": [
|
|
12
|
+
"npx",
|
|
13
|
+
"-y",
|
|
14
|
+
"mcp-remote",
|
|
15
|
+
"https://mcp.linear.app/sse"
|
|
16
|
+
],
|
|
17
|
+
"enabled": true
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
"agent": {
|
|
21
|
+
"gv-fetcher": {
|
|
22
|
+
"prompt": "{file:prompts/fetcher.md}",
|
|
23
|
+
"tools": {
|
|
24
|
+
"sentry_find_organizations": true,
|
|
25
|
+
"sentry_find_projects": true,
|
|
26
|
+
"sentry_search_issues": true,
|
|
27
|
+
"write": true,
|
|
28
|
+
"read": true
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
"gv-rca": {
|
|
32
|
+
"prompt": "{file:prompts/rca.md}",
|
|
33
|
+
"tools": {
|
|
34
|
+
"sentry_find_organizations": true,
|
|
35
|
+
"sentry_get_issue_details": true,
|
|
36
|
+
"sentry_analyze_issue_with_seer": true,
|
|
37
|
+
"read": true,
|
|
38
|
+
"notify": true
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
"gv-fixer": {
|
|
42
|
+
"prompt": "{file:prompts/fixer.md}",
|
|
43
|
+
"tools": {
|
|
44
|
+
"edit": true,
|
|
45
|
+
"bash": true,
|
|
46
|
+
"read": true,
|
|
47
|
+
"write": true,
|
|
48
|
+
"notify": true
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
},
|
|
52
|
+
"permission": {
|
|
53
|
+
"bash": "allow",
|
|
54
|
+
"edit": "allow",
|
|
55
|
+
"write": "allow",
|
|
56
|
+
"read": "allow"
|
|
57
|
+
}
|
|
58
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
tools:
|
|
2
|
+
bash: false
|
|
3
|
+
write: true
|
|
4
|
+
edit: false
|
|
5
|
+
sentry_find_organizations: true
|
|
6
|
+
sentry_find_projects: true
|
|
7
|
+
sentry_search_events: false
|
|
8
|
+
sentry_search_issues: true
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
You are the **Issue Fetcher & Prioritizer Agent**.
|
|
12
|
+
|
|
13
|
+
### TASK
|
|
14
|
+
Your goal is to identify the most critical Sentry issues for the requested projects and save them for processing.
|
|
15
|
+
|
|
16
|
+
### INSTRUCTIONS
|
|
17
|
+
1. **Identify Targets**:
|
|
18
|
+
* You have been provided a list of **Sentry Projects** in the prompt.
|
|
19
|
+
* Use your available Sentry tools to resolve the Organization and Project Slugs necessary to query them.
|
|
20
|
+
2. **Query Sentry**:
|
|
21
|
+
* Search for unresolved issues (`is:unresolved level:error`) in these projects.
|
|
22
|
+
* Limit: 20 issues per project.
|
|
23
|
+
3. **Prioritize**:
|
|
24
|
+
* Score issues (0-100) based on User Count, Event Frequency, and Age.
|
|
25
|
+
* Select the **Top 5** highest scoring issues.
|
|
26
|
+
4. **Output**:
|
|
27
|
+
* Read `state.json` (if exists) and exclude IDs in `processed_ids`.
|
|
28
|
+
* Write the filtered list to `issues.json` in the current directory.
|
|
29
|
+
|
|
30
|
+
### OUTPUT FORMAT (issues.json)
|
|
31
|
+
```json
|
|
32
|
+
[
|
|
33
|
+
{
|
|
34
|
+
"id": "SENTRY-123",
|
|
35
|
+
"title": "Error Title",
|
|
36
|
+
"organization": "org-slug",
|
|
37
|
+
"project": "project-slug",
|
|
38
|
+
"priority_score": 95,
|
|
39
|
+
"reason": "High impact"
|
|
40
|
+
}
|
|
41
|
+
]
|
|
42
|
+
```
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Implement code fixes and create Pull Requests
|
|
3
|
+
mode: primary
|
|
4
|
+
tools:
|
|
5
|
+
bash: true
|
|
6
|
+
write: true
|
|
7
|
+
edit: true
|
|
8
|
+
read: true
|
|
9
|
+
notify: true
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
You are the **Fixer Agent**.
|
|
13
|
+
|
|
14
|
+
### TASK
|
|
15
|
+
Implement the fix proposed in the RCA and submit a Pull Request.
|
|
16
|
+
|
|
17
|
+
### INPUT
|
|
18
|
+
* Read `rca.md` in the current directory.
|
|
19
|
+
* The current directory is `.gv-ai/runs/<timestamp>/<issue_id>/`.
|
|
20
|
+
|
|
21
|
+
### INSTRUCTIONS
|
|
22
|
+
1. **Preparation**:
|
|
23
|
+
* Read `rca.md` to understand the plan.
|
|
24
|
+
* **Git**: Ensure you are on the base branch (usually `main` or `master`). Pull latest.
|
|
25
|
+
* **Branch**: Create a new branch: `fix/sentry-<issue_id>-<timestamp>`.
|
|
26
|
+
2. **Implementation**:
|
|
27
|
+
* Use `edit` to modify the code.
|
|
28
|
+
* **Verify**: If possible, run a relevant test (or create a temporary test case) to ensure the fix works and doesn't break syntax.
|
|
29
|
+
* **Lint**: Run `npm run lint` (or equivalent) if available.
|
|
30
|
+
3. **Commit & PR**:
|
|
31
|
+
* `git commit -am "fix(sentry): Resolve <issue_id> - <brief description>"`
|
|
32
|
+
* `git push -u origin <branch_name>`
|
|
33
|
+
* Create PR using `gh pr create`:
|
|
34
|
+
* Title: `fix: Resolve Sentry Issue <issue_id>`
|
|
35
|
+
* Body: Include summary from `rca.md`.
|
|
36
|
+
4. **Notify**:
|
|
37
|
+
* Use `notify` tool: "✅ *Fix Implemented* for [Issue ID]\nPR: [PR URL]"
|
|
38
|
+
5. **Artifact**:
|
|
39
|
+
* Write `fix_result.json` with the PR URL and status.
|
|
40
|
+
|
|
41
|
+
### CONSTRAINTS
|
|
42
|
+
* Do NOT change unrelated code.
|
|
43
|
+
* If you cannot verify the fix (e.g., tests fail), abort and notify with a clear error message.
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
tools:
|
|
2
|
+
bash: true
|
|
3
|
+
write: true
|
|
4
|
+
edit: false
|
|
5
|
+
read: true
|
|
6
|
+
sentry_find_organizations: true
|
|
7
|
+
sentry_get_issue_details: true
|
|
8
|
+
sentry_get_issue_tag_values: true
|
|
9
|
+
sentry_analyze_issue_with_seer: true
|
|
10
|
+
notify: true
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
You are the **RCA (Root Cause Analysis) Agent**.
|
|
14
|
+
|
|
15
|
+
### TASK
|
|
16
|
+
Analyze the provided Sentry issue to determine the root cause and fixability.
|
|
17
|
+
|
|
18
|
+
### INPUT
|
|
19
|
+
* **Issue ID** (e.g., `SENTRY-123`) provided in the prompt.
|
|
20
|
+
* **Context**: You are running in `.gv-ai/runs/<timestamp>/<issue_id>/`.
|
|
21
|
+
|
|
22
|
+
### INSTRUCTIONS
|
|
23
|
+
1. **Analyze Issue**:
|
|
24
|
+
* Use your tools (`sentry_get_issue_details`, `sentry_analyze_issue_with_seer`) to inspect the issue.
|
|
25
|
+
* If you need the Organization slug and it wasn't provided, use your tools to find it.
|
|
26
|
+
* **Verify Code**: Check the local codebase (`grep`/`read`) to confirm the file and line number from the stack trace exist.
|
|
27
|
+
2. **Determine Root Cause**:
|
|
28
|
+
* Identify the logic error.
|
|
29
|
+
* Assess **Fixability** (High/Medium/Low).
|
|
30
|
+
3. **Report**:
|
|
31
|
+
* Write `rca.md` with your findings.
|
|
32
|
+
4. **Notify**:
|
|
33
|
+
* Send a Telegram summary using the `notify` tool.
|
|
34
|
+
|
|
35
|
+
### OUTPUT FORMAT (rca.md)
|
|
36
|
+
```markdown
|
|
37
|
+
# RCA: SENTRY-123
|
|
38
|
+
## Root Cause
|
|
39
|
+
...
|
|
40
|
+
## Evidence
|
|
41
|
+
...
|
|
42
|
+
## Proposed Fix
|
|
43
|
+
...
|
|
44
|
+
## Assessment
|
|
45
|
+
- **Confidence**: High/Medium/Low
|
|
46
|
+
- **Action**: Fix / Ticket / Skip
|
|
47
|
+
```
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
/Users/souvikmahapatra/projects/gv-ai/.gv-ai/scripts/orchestrator.sh: line 55: opencode: command not found
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
|
|
3
|
+
# Ensure we have a path (Cron often has limited path)
|
|
4
|
+
export PATH="/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:$PATH"
|
|
5
|
+
|
|
6
|
+
# Try to source user profile for NVM/Node/etc
|
|
7
|
+
if [ -f "$HOME/.bashrc" ]; then source "$HOME/.bashrc"; fi
|
|
8
|
+
if [ -f "$HOME/.zshrc" ]; then source "$HOME/.zshrc"; fi
|
|
9
|
+
if [ -f "$HOME/.profile" ]; then source "$HOME/.profile"; fi
|
|
10
|
+
|
|
11
|
+
# Directory Setup
|
|
12
|
+
# Resolve the directory where this script resides (.gv-ai/scripts)
|
|
13
|
+
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
|
|
14
|
+
# Base dir is parent of scripts (.gv-ai)
|
|
15
|
+
BASE_DIR="$(dirname "$SCRIPT_DIR")"
|
|
16
|
+
# Project Root is parent of .gv-ai
|
|
17
|
+
PROJECT_ROOT="$(dirname "$BASE_DIR")"
|
|
18
|
+
|
|
19
|
+
RUN_ID=$(date +%Y%m%d-%H%M%S)
|
|
20
|
+
RUN_DIR="$BASE_DIR/runs/$RUN_ID"
|
|
21
|
+
STATE_FILE="$BASE_DIR/state.json"
|
|
22
|
+
ENV_FILE="$BASE_DIR/env.json"
|
|
23
|
+
|
|
24
|
+
mkdir -p "$RUN_DIR"
|
|
25
|
+
|
|
26
|
+
# Load Environment Variables from env.json
|
|
27
|
+
if [ -f "$ENV_FILE" ]; then
|
|
28
|
+
# Simple parser for flat JSON string/number values
|
|
29
|
+
while IFS='=' read -r key value; do
|
|
30
|
+
# Remove quotes and comma
|
|
31
|
+
key=$(echo "$key" | tr -d '",')
|
|
32
|
+
value=$(echo "$value" | tr -d '",')
|
|
33
|
+
# Export if not empty
|
|
34
|
+
if [[ ! -z "$key" && "$key" != "{" && "$key" != "}" ]]; then
|
|
35
|
+
export "$key=$value"
|
|
36
|
+
fi
|
|
37
|
+
done < <(cat "$ENV_FILE" | grep ":")
|
|
38
|
+
fi
|
|
39
|
+
|
|
40
|
+
# Export Config Dir for OpenCode to find custom tools
|
|
41
|
+
export OPENCODE_CONFIG_DIR="$BASE_DIR"
|
|
42
|
+
|
|
43
|
+
echo "🚀 Starting Sentry Resolver Run: $RUN_ID"
|
|
44
|
+
echo "📂 Project Root: $PROJECT_ROOT"
|
|
45
|
+
|
|
46
|
+
# ---------------------------------------------------------
|
|
47
|
+
# Phase 1: Fetch Issues
|
|
48
|
+
# ---------------------------------------------------------
|
|
49
|
+
echo "🔍 Fetching issues..."
|
|
50
|
+
cd "$RUN_DIR" || exit 1
|
|
51
|
+
|
|
52
|
+
# Run opencode from Project Root so it can access project files if needed
|
|
53
|
+
cd "$PROJECT_ROOT" || exit 1
|
|
54
|
+
|
|
55
|
+
opencode run \
|
|
56
|
+
--agent gv-fetcher \
|
|
57
|
+
--prompt "Fetch critical issues for projects: ${SENTRY_PROJECTS}. Write output to $RUN_DIR/issues.json" \
|
|
58
|
+
> "$RUN_DIR/fetcher.log" 2>&1
|
|
59
|
+
|
|
60
|
+
if [ ! -f "$RUN_DIR/issues.json" ]; then
|
|
61
|
+
echo "⚠️ No issues.json created. Check $RUN_DIR/fetcher.log"
|
|
62
|
+
exit 0
|
|
63
|
+
fi
|
|
64
|
+
|
|
65
|
+
# ---------------------------------------------------------
|
|
66
|
+
# Phase 2: Process Loop
|
|
67
|
+
# ---------------------------------------------------------
|
|
68
|
+
# Use node to parse the issues.json because bash JSON parsing is painful
|
|
69
|
+
node -e "
|
|
70
|
+
const fs = require('fs');
|
|
71
|
+
const issues = JSON.parse(fs.readFileSync('$RUN_DIR/issues.json', 'utf8'));
|
|
72
|
+
issues.forEach(issue => console.log(issue.id));
|
|
73
|
+
" > "$RUN_DIR/issue_list.txt"
|
|
74
|
+
|
|
75
|
+
while read -r ISSUE_ID; do
|
|
76
|
+
if [ -z "$ISSUE_ID" ]; then continue; fi
|
|
77
|
+
|
|
78
|
+
echo "⚙️ Processing Issue: $ISSUE_ID"
|
|
79
|
+
ISSUE_DIR="$RUN_DIR/$ISSUE_ID"
|
|
80
|
+
mkdir -p "$ISSUE_DIR"
|
|
81
|
+
|
|
82
|
+
# RCA Phase
|
|
83
|
+
echo " - Running RCA..."
|
|
84
|
+
opencode run \
|
|
85
|
+
--agent gv-rca \
|
|
86
|
+
--prompt "Perform RCA for $ISSUE_ID. Working directory: $ISSUE_DIR" \
|
|
87
|
+
> "$ISSUE_DIR/rca.log" 2>&1
|
|
88
|
+
|
|
89
|
+
if [ ! -f "$ISSUE_DIR/rca.md" ]; then
|
|
90
|
+
echo " ⚠️ RCA failed to produce report."
|
|
91
|
+
continue
|
|
92
|
+
fi
|
|
93
|
+
|
|
94
|
+
# Decision Check
|
|
95
|
+
# We look for 'Action: Fix' in the RCA markdown
|
|
96
|
+
ACTION=$(grep -i "Action:" "$ISSUE_DIR/rca.md" | head -n 1)
|
|
97
|
+
|
|
98
|
+
if [[ "$ACTION" == *"Fix"* ]]; then
|
|
99
|
+
echo " - Decision: FIX. Running Fixer..."
|
|
100
|
+
opencode run \
|
|
101
|
+
--agent gv-fixer \
|
|
102
|
+
--prompt "Fix $ISSUE_ID. RCA is at $ISSUE_DIR/rca.md" \
|
|
103
|
+
> "$ISSUE_DIR/fixer.log" 2>&1
|
|
104
|
+
else
|
|
105
|
+
echo " - Decision: SKIP/TICKET ($ACTION)"
|
|
106
|
+
fi
|
|
107
|
+
|
|
108
|
+
# Update State (Append to processed list)
|
|
109
|
+
# In a real impl, we'd read/write JSON properly. For now, we assume fetcher handles filtering.
|
|
110
|
+
# But we should append to a processed log.
|
|
111
|
+
echo "$ISSUE_ID processed at $(date)" >> "$BASE_DIR/processed_log.txt"
|
|
112
|
+
|
|
113
|
+
done < "$RUN_DIR/issue_list.txt"
|
|
114
|
+
|
|
115
|
+
echo "✅ Run Complete."
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { tool } from "@opencode-ai/plugin";
|
|
2
|
+
|
|
3
|
+
export default tool({
|
|
4
|
+
description: "Send a notification via Telegram",
|
|
5
|
+
args: {
|
|
6
|
+
message: tool.schema.string().describe("The message to send"),
|
|
7
|
+
},
|
|
8
|
+
async execute(args) {
|
|
9
|
+
const fs = await import("fs/promises");
|
|
10
|
+
const path = await import("path");
|
|
11
|
+
|
|
12
|
+
// Load secrets from .gv-ai/env.json (this file is in .gv-ai/tool/)
|
|
13
|
+
const envPath = path.join(__dirname, "..", "env.json");
|
|
14
|
+
|
|
15
|
+
let config;
|
|
16
|
+
try {
|
|
17
|
+
const envContent = await fs.readFile(envPath, "utf-8");
|
|
18
|
+
config = JSON.parse(envContent);
|
|
19
|
+
} catch {
|
|
20
|
+
return "Error: Could not load .gv-ai/env.json";
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const { TELEGRAM_BOT_TOKEN, TELEGRAM_CHAT_ID } = config;
|
|
24
|
+
if (!TELEGRAM_BOT_TOKEN || !TELEGRAM_CHAT_ID) {
|
|
25
|
+
return "Error: Telegram credentials missing in env.json";
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const text = `🤖 *Tal Auto-Fixer*\n\n${args.message}`;
|
|
29
|
+
|
|
30
|
+
try {
|
|
31
|
+
const res = await fetch(
|
|
32
|
+
`https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/sendMessage`,
|
|
33
|
+
{
|
|
34
|
+
method: "POST",
|
|
35
|
+
headers: { "Content-Type": "application/json" },
|
|
36
|
+
body: JSON.stringify({
|
|
37
|
+
chat_id: TELEGRAM_CHAT_ID,
|
|
38
|
+
text,
|
|
39
|
+
parse_mode: "Markdown",
|
|
40
|
+
}),
|
|
41
|
+
}
|
|
42
|
+
);
|
|
43
|
+
const result = await res.json();
|
|
44
|
+
return result.ok
|
|
45
|
+
? "Notification sent."
|
|
46
|
+
: `Telegram Error: ${result.description}`;
|
|
47
|
+
} catch (e: any) {
|
|
48
|
+
return `Network Error: ${e.message}`;
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
});
|
package/dist/cli.js
CHANGED
|
@@ -5508,6 +5508,7 @@ var {
|
|
|
5508
5508
|
var isUpKey = (key, keybindings = []) => key.name === "up" || keybindings.includes("vim") && key.name === "k" || keybindings.includes("emacs") && key.ctrl && key.name === "p";
|
|
5509
5509
|
var isDownKey = (key, keybindings = []) => key.name === "down" || keybindings.includes("vim") && key.name === "j" || keybindings.includes("emacs") && key.ctrl && key.name === "n";
|
|
5510
5510
|
var isSpaceKey = (key) => key.name === "space";
|
|
5511
|
+
var isBackspaceKey = (key) => key.name === "backspace";
|
|
5511
5512
|
var isTabKey = (key) => key.name === "tab";
|
|
5512
5513
|
var isNumberKey = (key) => "1234567890".includes(key.name);
|
|
5513
5514
|
var isEnterKey = (key) => key.name === "enter" || key.name === "return";
|
|
@@ -6802,6 +6803,90 @@ var esm_default3 = createPrompt((config, done) => {
|
|
|
6802
6803
|
const message = theme.style.message(config.message, status);
|
|
6803
6804
|
return `${prefix} ${message}${defaultValue} ${formattedValue}`;
|
|
6804
6805
|
});
|
|
6806
|
+
// node_modules/@inquirer/input/dist/esm/index.js
|
|
6807
|
+
var inputTheme = {
|
|
6808
|
+
validationFailureMode: "keep"
|
|
6809
|
+
};
|
|
6810
|
+
var esm_default4 = createPrompt((config, done) => {
|
|
6811
|
+
const { prefill = "tab" } = config;
|
|
6812
|
+
const theme = makeTheme(inputTheme, config.theme);
|
|
6813
|
+
const [status, setStatus] = useState("idle");
|
|
6814
|
+
const [defaultValue = "", setDefaultValue] = useState(config.default);
|
|
6815
|
+
const [errorMsg, setError] = useState();
|
|
6816
|
+
const [value, setValue] = useState("");
|
|
6817
|
+
const prefix = usePrefix({ status, theme });
|
|
6818
|
+
async function validate(value2) {
|
|
6819
|
+
const { required, pattern, patternError = "Invalid input" } = config;
|
|
6820
|
+
if (required && !value2) {
|
|
6821
|
+
return "You must provide a value";
|
|
6822
|
+
}
|
|
6823
|
+
if (pattern && !pattern.test(value2)) {
|
|
6824
|
+
return patternError;
|
|
6825
|
+
}
|
|
6826
|
+
if (typeof config.validate === "function") {
|
|
6827
|
+
return await config.validate(value2) || "You must provide a valid value";
|
|
6828
|
+
}
|
|
6829
|
+
return true;
|
|
6830
|
+
}
|
|
6831
|
+
useKeypress(async (key, rl) => {
|
|
6832
|
+
if (status !== "idle") {
|
|
6833
|
+
return;
|
|
6834
|
+
}
|
|
6835
|
+
if (isEnterKey(key)) {
|
|
6836
|
+
const answer = value || defaultValue;
|
|
6837
|
+
setStatus("loading");
|
|
6838
|
+
const isValid = await validate(answer);
|
|
6839
|
+
if (isValid === true) {
|
|
6840
|
+
setValue(answer);
|
|
6841
|
+
setStatus("done");
|
|
6842
|
+
done(answer);
|
|
6843
|
+
} else {
|
|
6844
|
+
if (theme.validationFailureMode === "clear") {
|
|
6845
|
+
setValue("");
|
|
6846
|
+
} else {
|
|
6847
|
+
rl.write(value);
|
|
6848
|
+
}
|
|
6849
|
+
setError(isValid);
|
|
6850
|
+
setStatus("idle");
|
|
6851
|
+
}
|
|
6852
|
+
} else if (isBackspaceKey(key) && !value) {
|
|
6853
|
+
setDefaultValue(undefined);
|
|
6854
|
+
} else if (isTabKey(key) && !value) {
|
|
6855
|
+
setDefaultValue(undefined);
|
|
6856
|
+
rl.clearLine(0);
|
|
6857
|
+
rl.write(defaultValue);
|
|
6858
|
+
setValue(defaultValue);
|
|
6859
|
+
} else {
|
|
6860
|
+
setValue(rl.line);
|
|
6861
|
+
setError(undefined);
|
|
6862
|
+
}
|
|
6863
|
+
});
|
|
6864
|
+
useEffect((rl) => {
|
|
6865
|
+
if (prefill === "editable" && defaultValue) {
|
|
6866
|
+
rl.write(defaultValue);
|
|
6867
|
+
setValue(defaultValue);
|
|
6868
|
+
}
|
|
6869
|
+
}, []);
|
|
6870
|
+
const message = theme.style.message(config.message, status);
|
|
6871
|
+
let formattedValue = value;
|
|
6872
|
+
if (typeof config.transformer === "function") {
|
|
6873
|
+
formattedValue = config.transformer(value, { isFinal: status === "done" });
|
|
6874
|
+
} else if (status === "done") {
|
|
6875
|
+
formattedValue = theme.style.answer(value);
|
|
6876
|
+
}
|
|
6877
|
+
let defaultStr;
|
|
6878
|
+
if (defaultValue && status !== "done" && !value) {
|
|
6879
|
+
defaultStr = theme.style.defaultAnswer(defaultValue);
|
|
6880
|
+
}
|
|
6881
|
+
let error = "";
|
|
6882
|
+
if (errorMsg) {
|
|
6883
|
+
error = theme.style.error(errorMsg);
|
|
6884
|
+
}
|
|
6885
|
+
return [
|
|
6886
|
+
[prefix, message, defaultStr, formattedValue].filter((v) => v !== undefined).join(" "),
|
|
6887
|
+
error
|
|
6888
|
+
];
|
|
6889
|
+
});
|
|
6805
6890
|
// node_modules/chalk/source/vendor/ansi-styles/index.js
|
|
6806
6891
|
var ANSI_BACKGROUND_OFFSET = 10;
|
|
6807
6892
|
var wrapAnsi16 = (offset = 0) => (code) => `\x1B[${code + offset}m`;
|
|
@@ -14910,8 +14995,258 @@ Step 3: Creating instruction files...
|
|
|
14910
14995
|
}
|
|
14911
14996
|
}
|
|
14912
14997
|
|
|
14998
|
+
// src/commands/sentry-resolver.ts
|
|
14999
|
+
import fs3 from "fs/promises";
|
|
15000
|
+
import path8 from "path";
|
|
15001
|
+
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
15002
|
+
var __filename2 = fileURLToPath3(import.meta.url);
|
|
15003
|
+
var __dirname2 = path8.dirname(__filename2);
|
|
15004
|
+
async function sentryResolverInitCommand() {
|
|
15005
|
+
console.log(source_default.bold.cyan(`
|
|
15006
|
+
\uD83D\uDEE1️ GV-AI Sentry Resolver Setup
|
|
15007
|
+
`));
|
|
15008
|
+
console.log(source_default.dim(`This will configure an automated agent to triage and fix Sentry issues.
|
|
15009
|
+
`));
|
|
15010
|
+
try {
|
|
15011
|
+
const baseDir = path8.join(process.cwd(), ".gv-ai");
|
|
15012
|
+
try {
|
|
15013
|
+
await fs3.access(baseDir);
|
|
15014
|
+
console.log(source_default.yellow(`
|
|
15015
|
+
⚠️ .gv-ai directory already exists.`));
|
|
15016
|
+
const shouldOverwrite = await esm_default3({
|
|
15017
|
+
message: "Do you want to overwrite the existing configuration? (This will reset env.json and prompts)",
|
|
15018
|
+
default: false
|
|
15019
|
+
});
|
|
15020
|
+
if (!shouldOverwrite) {
|
|
15021
|
+
console.log(source_default.yellow("Setup cancelled. Existing configuration preserved."));
|
|
15022
|
+
return;
|
|
15023
|
+
}
|
|
15024
|
+
} catch {}
|
|
15025
|
+
const sentryProjects = await esm_default4({ message: "Sentry Project Names (comma separated):" });
|
|
15026
|
+
const telegramToken = await esm_default4({ message: "Telegram Bot Token:" });
|
|
15027
|
+
const telegramChatId = await esm_default4({ message: "Telegram Chat ID:" });
|
|
15028
|
+
const config = {
|
|
15029
|
+
SENTRY_PROJECTS: sentryProjects,
|
|
15030
|
+
TELEGRAM_BOT_TOKEN: telegramToken,
|
|
15031
|
+
TELEGRAM_CHAT_ID: telegramChatId
|
|
15032
|
+
};
|
|
15033
|
+
const spinner = ora("Creating .gv-ai directory structure...").start();
|
|
15034
|
+
await fs3.mkdir(path8.join(baseDir, "runs"), { recursive: true });
|
|
15035
|
+
await fs3.mkdir(path8.join(baseDir, "scripts"), { recursive: true });
|
|
15036
|
+
await fs3.mkdir(path8.join(baseDir, "prompts"), { recursive: true });
|
|
15037
|
+
await fs3.mkdir(path8.join(baseDir, "tool"), { recursive: true });
|
|
15038
|
+
await fs3.writeFile(path8.join(baseDir, "env.json"), JSON.stringify(config, null, 2));
|
|
15039
|
+
spinner.succeed("Created .gv-ai/ and env.json");
|
|
15040
|
+
const templateSpinner = ora("Copying templates...").start();
|
|
15041
|
+
const templateDirCandidates = [
|
|
15042
|
+
path8.join(__dirname2, "../templates/sentry-resolver"),
|
|
15043
|
+
path8.join(__dirname2, "templates/sentry-resolver"),
|
|
15044
|
+
path8.join(__dirname2, "../src/templates/sentry-resolver")
|
|
15045
|
+
];
|
|
15046
|
+
let templateDir = "";
|
|
15047
|
+
for (const dir of templateDirCandidates) {
|
|
15048
|
+
try {
|
|
15049
|
+
await fs3.access(dir);
|
|
15050
|
+
templateDir = dir;
|
|
15051
|
+
break;
|
|
15052
|
+
} catch {}
|
|
15053
|
+
}
|
|
15054
|
+
if (!templateDir) {
|
|
15055
|
+
throw new Error(`Could not locate templates. Searched: ${templateDirCandidates.join(", ")}`);
|
|
15056
|
+
}
|
|
15057
|
+
await fs3.copyFile(path8.join(templateDir, "prompts/fetcher.md"), path8.join(baseDir, "prompts/fetcher.md"));
|
|
15058
|
+
await fs3.copyFile(path8.join(templateDir, "prompts/rca.md"), path8.join(baseDir, "prompts/rca.md"));
|
|
15059
|
+
await fs3.copyFile(path8.join(templateDir, "prompts/fixer.md"), path8.join(baseDir, "prompts/fixer.md"));
|
|
15060
|
+
await fs3.copyFile(path8.join(templateDir, "scripts/orchestrator.sh"), path8.join(baseDir, "scripts/orchestrator.sh"));
|
|
15061
|
+
await fs3.chmod(path8.join(baseDir, "scripts/orchestrator.sh"), 493);
|
|
15062
|
+
await fs3.copyFile(path8.join(templateDir, "tool/notify.ts.template"), path8.join(baseDir, "tool/notify.ts"));
|
|
15063
|
+
templateSpinner.succeed("Templates copied");
|
|
15064
|
+
const configSpinner = ora("Generating bot configuration...").start();
|
|
15065
|
+
const botConfig = {
|
|
15066
|
+
$schema: "https://opencode.ai/config.json",
|
|
15067
|
+
mcp: {
|
|
15068
|
+
sentry: {
|
|
15069
|
+
type: "remote",
|
|
15070
|
+
url: "https://mcp.sentry.dev/mcp",
|
|
15071
|
+
oauth: {}
|
|
15072
|
+
},
|
|
15073
|
+
linear: {
|
|
15074
|
+
type: "local",
|
|
15075
|
+
command: ["npx", "-y", "mcp-remote", "https://mcp.linear.app/sse"],
|
|
15076
|
+
enabled: true
|
|
15077
|
+
}
|
|
15078
|
+
},
|
|
15079
|
+
agent: {
|
|
15080
|
+
"gv-fetcher": {
|
|
15081
|
+
prompt: "{file:prompts/fetcher.md}",
|
|
15082
|
+
tools: {
|
|
15083
|
+
sentry_find_organizations: true,
|
|
15084
|
+
sentry_find_projects: true,
|
|
15085
|
+
sentry_search_issues: true,
|
|
15086
|
+
write: true,
|
|
15087
|
+
read: true
|
|
15088
|
+
}
|
|
15089
|
+
},
|
|
15090
|
+
"gv-rca": {
|
|
15091
|
+
prompt: "{file:prompts/rca.md}",
|
|
15092
|
+
tools: {
|
|
15093
|
+
sentry_find_organizations: true,
|
|
15094
|
+
sentry_get_issue_details: true,
|
|
15095
|
+
sentry_analyze_issue_with_seer: true,
|
|
15096
|
+
read: true,
|
|
15097
|
+
notify: true
|
|
15098
|
+
}
|
|
15099
|
+
},
|
|
15100
|
+
"gv-fixer": {
|
|
15101
|
+
prompt: "{file:prompts/fixer.md}",
|
|
15102
|
+
tools: {
|
|
15103
|
+
edit: true,
|
|
15104
|
+
bash: true,
|
|
15105
|
+
read: true,
|
|
15106
|
+
write: true,
|
|
15107
|
+
notify: true
|
|
15108
|
+
}
|
|
15109
|
+
}
|
|
15110
|
+
},
|
|
15111
|
+
permission: {
|
|
15112
|
+
bash: "allow",
|
|
15113
|
+
edit: "allow",
|
|
15114
|
+
write: "allow",
|
|
15115
|
+
read: "allow"
|
|
15116
|
+
}
|
|
15117
|
+
};
|
|
15118
|
+
await fs3.writeFile(path8.join(baseDir, "opencode.json"), JSON.stringify(botConfig, null, 2));
|
|
15119
|
+
configSpinner.succeed("Bot configuration generated (.gv-ai/opencode.json)");
|
|
15120
|
+
const cronSpinner = ora("Setting up cron job...").start();
|
|
15121
|
+
const scriptPath = path8.join(baseDir, "scripts/orchestrator.sh");
|
|
15122
|
+
const cronSchedule = "0 */4 * * *";
|
|
15123
|
+
const cronJob = `${cronSchedule} ${scriptPath} >> ${path8.join(baseDir, "cron.log")} 2>&1`;
|
|
15124
|
+
try {
|
|
15125
|
+
let currentCrontab = "";
|
|
15126
|
+
try {
|
|
15127
|
+
const { stdout } = await execa("crontab", ["-l"]);
|
|
15128
|
+
currentCrontab = stdout;
|
|
15129
|
+
} catch (e) {}
|
|
15130
|
+
if (!currentCrontab.includes(scriptPath)) {
|
|
15131
|
+
const newCrontab = currentCrontab + (currentCrontab ? `
|
|
15132
|
+
` : "") + cronJob + `
|
|
15133
|
+
`;
|
|
15134
|
+
await execa("sh", ["-c", `echo "${newCrontab}" | crontab -`]);
|
|
15135
|
+
cronSpinner.succeed("Cron job registered");
|
|
15136
|
+
} else {
|
|
15137
|
+
cronSpinner.info("Cron job already exists");
|
|
15138
|
+
}
|
|
15139
|
+
} catch (e) {
|
|
15140
|
+
cronSpinner.warn(`Failed to set crontab: ${e.message}. You may need to add it manually.`);
|
|
15141
|
+
console.log(source_default.dim(`Manual entry: ${cronJob}`));
|
|
15142
|
+
}
|
|
15143
|
+
try {
|
|
15144
|
+
const gitignorePath = path8.join(process.cwd(), ".gitignore");
|
|
15145
|
+
const gitignore = await fs3.readFile(gitignorePath, "utf-8").catch(() => "");
|
|
15146
|
+
const entry = `
|
|
15147
|
+
# GV-AI Automation
|
|
15148
|
+
.gv-ai/`;
|
|
15149
|
+
if (!gitignore.includes(".gv-ai")) {
|
|
15150
|
+
await fs3.appendFile(gitignorePath, entry + `
|
|
15151
|
+
`);
|
|
15152
|
+
console.log(source_default.green("Added .gv-ai/ to .gitignore"));
|
|
15153
|
+
}
|
|
15154
|
+
} catch (e) {}
|
|
15155
|
+
console.log(source_default.bold.green(`
|
|
15156
|
+
✅ Initialization Complete!`));
|
|
15157
|
+
console.log(source_default.dim(`
|
|
15158
|
+
- Config: .gv-ai/env.json
|
|
15159
|
+
- Logs: .gv-ai/runs/
|
|
15160
|
+
- Cron: Runs every 5 minutes
|
|
15161
|
+
|
|
15162
|
+
To test manually:
|
|
15163
|
+
$ .gv-ai/scripts/orchestrator.sh
|
|
15164
|
+
`));
|
|
15165
|
+
} catch (error) {
|
|
15166
|
+
console.error(source_default.red(`
|
|
15167
|
+
❌ Error: ${error.message}`));
|
|
15168
|
+
process.exit(1);
|
|
15169
|
+
}
|
|
15170
|
+
}
|
|
15171
|
+
async function sentryResolverCronSetCommand(value) {
|
|
15172
|
+
const baseDir = path8.join(process.cwd(), ".gv-ai");
|
|
15173
|
+
const scriptPath = path8.join(baseDir, "scripts/orchestrator.sh");
|
|
15174
|
+
const cronRegex = /^(@(annually|yearly|monthly|weekly|daily|hourly|reboot))|(\*|[0-9,\-\/]+)\s+(\*|[0-9,\-\/]+)\s+(\*|[0-9,\-\/]+)\s+(\*|[0-9,\-\/]+)\s+(\*|[0-9,\-\/]+)$/;
|
|
15175
|
+
let cronSchedule = "";
|
|
15176
|
+
if (value.endsWith("m")) {
|
|
15177
|
+
const mins = parseInt(value.slice(0, -1));
|
|
15178
|
+
if (isNaN(mins) || mins < 1 || mins > 59) {
|
|
15179
|
+
console.error(source_default.red(`❌ Invalid minutes: ${value}. Use format: 5m, 10m, 30m`));
|
|
15180
|
+
process.exit(1);
|
|
15181
|
+
}
|
|
15182
|
+
cronSchedule = `*/${mins} * * * *`;
|
|
15183
|
+
} else if (value.endsWith("h")) {
|
|
15184
|
+
const hours = parseInt(value.slice(0, -1));
|
|
15185
|
+
if (isNaN(hours) || hours < 1 || hours > 23) {
|
|
15186
|
+
console.error(source_default.red(`❌ Invalid hours: ${value}. Use format: 1h, 4h, 12h`));
|
|
15187
|
+
process.exit(1);
|
|
15188
|
+
}
|
|
15189
|
+
cronSchedule = `0 */${hours} * * *`;
|
|
15190
|
+
} else if (cronRegex.test(value)) {
|
|
15191
|
+
cronSchedule = value;
|
|
15192
|
+
} else {
|
|
15193
|
+
console.error(source_default.red(`❌ Invalid format. Use: 5m, 2h, or cron expression`));
|
|
15194
|
+
process.exit(1);
|
|
15195
|
+
}
|
|
15196
|
+
console.log(source_default.cyan(`
|
|
15197
|
+
⏰ Setting cron to: ${cronSchedule}`));
|
|
15198
|
+
try {
|
|
15199
|
+
let currentCrontab = "";
|
|
15200
|
+
try {
|
|
15201
|
+
const { stdout } = await execa("crontab", ["-l"]);
|
|
15202
|
+
currentCrontab = stdout;
|
|
15203
|
+
} catch {}
|
|
15204
|
+
const cleanedCrontab = currentCrontab.split(`
|
|
15205
|
+
`).filter((line) => !line.includes(scriptPath)).join(`
|
|
15206
|
+
`);
|
|
15207
|
+
const cronJob = `${cronSchedule} ${scriptPath} >> ${path8.join(baseDir, "cron.log")} 2>&1`;
|
|
15208
|
+
const newCrontab = (cleanedCrontab ? cleanedCrontab + `
|
|
15209
|
+
` : "") + cronJob + `
|
|
15210
|
+
`;
|
|
15211
|
+
await execa("sh", ["-c", `echo "${newCrontab}" | crontab -`]);
|
|
15212
|
+
console.log(source_default.green("✅ Cron job updated!"));
|
|
15213
|
+
} catch (e) {
|
|
15214
|
+
console.error(source_default.red(`❌ Failed to update cron: ${e.message}`));
|
|
15215
|
+
process.exit(1);
|
|
15216
|
+
}
|
|
15217
|
+
}
|
|
15218
|
+
async function sentryResolverCronDisableCommand() {
|
|
15219
|
+
const baseDir = path8.join(process.cwd(), ".gv-ai");
|
|
15220
|
+
const scriptPath = path8.join(baseDir, "scripts/orchestrator.sh");
|
|
15221
|
+
console.log(source_default.cyan(`
|
|
15222
|
+
\uD83D\uDED1 Disabling cron job...`));
|
|
15223
|
+
try {
|
|
15224
|
+
let currentCrontab = "";
|
|
15225
|
+
try {
|
|
15226
|
+
const { stdout } = await execa("crontab", ["-l"]);
|
|
15227
|
+
currentCrontab = stdout;
|
|
15228
|
+
} catch {}
|
|
15229
|
+
if (!currentCrontab.includes(scriptPath)) {
|
|
15230
|
+
console.log(source_default.yellow("Cron job not found."));
|
|
15231
|
+
return;
|
|
15232
|
+
}
|
|
15233
|
+
const cleanedCrontab = currentCrontab.split(`
|
|
15234
|
+
`).filter((line) => !line.includes(scriptPath)).join(`
|
|
15235
|
+
`);
|
|
15236
|
+
await execa("sh", ["-c", `echo "${cleanedCrontab}" | crontab -`]);
|
|
15237
|
+
console.log(source_default.green("✅ Cron job disabled!"));
|
|
15238
|
+
} catch (e) {
|
|
15239
|
+
console.error(source_default.red(`❌ Failed to disable cron: ${e.message}`));
|
|
15240
|
+
process.exit(1);
|
|
15241
|
+
}
|
|
15242
|
+
}
|
|
15243
|
+
|
|
14913
15244
|
// src/cli.ts
|
|
14914
15245
|
var program2 = new Command;
|
|
14915
15246
|
program2.name("gv-ai").description("CLI tool to enforce Linear ticket tracking across AI coding tools").version("1.0.0");
|
|
14916
15247
|
program2.command("project").description("Manage project configuration").command("init").description("Initialize GV AI project setup").action(initCommand);
|
|
15248
|
+
var sentryResolver = program2.command("sentry-resolver").description("Manage Sentry automated resolution");
|
|
15249
|
+
sentryResolver.command("init").description("Initialize Sentry Resolver bot").action(sentryResolverInitCommand);
|
|
15250
|
+
sentryResolver.command("cron").description("Manage cron schedule").command("set <interval>").description('Set cron interval (e.g., 5m, 2h, "*/10 * * * *")').action((interval) => sentryResolverCronSetCommand(interval));
|
|
15251
|
+
sentryResolver.command("disable").description("Disable the cron job").action(sentryResolverCronDisableCommand);
|
|
14917
15252
|
program2.parse();
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export declare function sentryResolverInitCommand(): Promise<void>;
|
|
2
|
+
export declare function sentryResolverCronSetCommand(value: string): Promise<void>;
|
|
3
|
+
export declare function sentryResolverCronDisableCommand(): Promise<void>;
|
|
4
|
+
//# sourceMappingURL=sentry-resolver.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sentry-resolver.d.ts","sourceRoot":"","sources":["../../src/commands/sentry-resolver.ts"],"names":[],"mappings":"AAWA,wBAAsB,yBAAyB,kBAkO9C;AAED,wBAAsB,4BAA4B,CAAC,KAAK,EAAE,MAAM,iBAuD/D;AAED,wBAAsB,gCAAgC,kBA6BrD"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"show-template.d.ts","sourceRoot":"","sources":["../../src/commands/show-template.ts"],"names":[],"mappings":"AAEA,wBAAsB,mBAAmB,kBAExC"}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
export const AGENTS_MD_TEMPLATE = `# AGENTS.md
|
|
2
|
+
|
|
3
|
+
## Linear Ticket Enforcement
|
|
4
|
+
|
|
5
|
+
**CRITICAL**: Before making ANY code changes (editing files, creating files, running commands that modify state), you MUST have a confirmed Linear ticket.
|
|
6
|
+
|
|
7
|
+
### Pre-Implementation Gate
|
|
8
|
+
|
|
9
|
+
When the user requests implementation work:
|
|
10
|
+
|
|
11
|
+
1. **Ask what they're working on** (if not clear from context)
|
|
12
|
+
2. **Search Linear for relevant tickets** using Linear MCP tools
|
|
13
|
+
3. **Fetch all Linear projects** to show available options
|
|
14
|
+
4. **Present a numbered list** to the user:
|
|
15
|
+
|
|
16
|
+
\`\`\`
|
|
17
|
+
Found potential matches:
|
|
18
|
+
1. [PROJ-123]: Ticket title here
|
|
19
|
+
2. [PROJ-456]: Another ticket title
|
|
20
|
+
|
|
21
|
+
Or create new ticket in:
|
|
22
|
+
3. Backend API (BACK)
|
|
23
|
+
4. Platform Infrastructure (PLAT)
|
|
24
|
+
5. Mobile App (MOB)
|
|
25
|
+
|
|
26
|
+
Which one? (enter number)
|
|
27
|
+
\`\`\`
|
|
28
|
+
|
|
29
|
+
5. **Wait for user to select a number**
|
|
30
|
+
6. **Confirm the selection** before proceeding with any implementation
|
|
31
|
+
|
|
32
|
+
### Free Actions (No Ticket Required)
|
|
33
|
+
|
|
34
|
+
You may freely help with:
|
|
35
|
+
- Answering questions about code
|
|
36
|
+
- Explaining how code works
|
|
37
|
+
- Discussing approaches and solutions
|
|
38
|
+
- Debugging and analysis (without making changes)
|
|
39
|
+
- Code review and suggestions
|
|
40
|
+
- General brainstorming
|
|
41
|
+
|
|
42
|
+
### Gated Actions (Ticket Required)
|
|
43
|
+
|
|
44
|
+
You MUST have a confirmed Linear ticket before:
|
|
45
|
+
- Creating new files
|
|
46
|
+
- Editing existing files
|
|
47
|
+
- Running commands that modify state (git commits, npm install, etc.)
|
|
48
|
+
- Creating branches or pull requests
|
|
49
|
+
- Deploying or publishing
|
|
50
|
+
|
|
51
|
+
### During Implementation
|
|
52
|
+
|
|
53
|
+
Once a ticket is confirmed:
|
|
54
|
+
- Update the Linear ticket with progress comments as you work
|
|
55
|
+
- Set ticket status to "In Progress" when starting
|
|
56
|
+
- Link commits and branches to the ticket
|
|
57
|
+
- Keep the ticket updated with any blockers or decisions
|
|
58
|
+
|
|
59
|
+
### PR Creation
|
|
60
|
+
|
|
61
|
+
When creating a pull request:
|
|
62
|
+
- Use Linear's branch naming convention (if available)
|
|
63
|
+
- Automatically link the PR to the associated ticket
|
|
64
|
+
- Update the ticket status to "In Review"
|
|
65
|
+
- Add a comment to the ticket with the PR link
|
|
66
|
+
|
|
67
|
+
### Multi-Ticket Sessions
|
|
68
|
+
|
|
69
|
+
If work touches multiple features or areas:
|
|
70
|
+
- Identify all relevant tickets at the start
|
|
71
|
+
- Confirm with the user which tickets to track
|
|
72
|
+
- Update each ticket appropriately as you work on related code
|
|
73
|
+
- Keep tickets in sync with the work being done
|
|
74
|
+
|
|
75
|
+
### Ad-hoc Tickets
|
|
76
|
+
|
|
77
|
+
When creating a new ad-hoc ticket:
|
|
78
|
+
- Ask the user which Linear project to create it in
|
|
79
|
+
- Use the user's description as the ticket title
|
|
80
|
+
- Automatically add an "ad-hoc" label
|
|
81
|
+
- Assign the ticket to the current user (if possible)
|
|
82
|
+
- Set the status to "In Progress"
|
|
83
|
+
|
|
84
|
+
### Important Notes
|
|
85
|
+
|
|
86
|
+
- **Always confirm** before associating work with a ticket
|
|
87
|
+
- **Never guess** which ticket applies - always ask the user
|
|
88
|
+
- **Show ticket details** (title, description preview) when asking for confirmation
|
|
89
|
+
- **Handle errors gracefully** if Linear API is unavailable
|
|
90
|
+
- **Be patient** - the user may need time to find the right ticket
|
|
91
|
+
`;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
tools:
|
|
2
|
+
bash: false
|
|
3
|
+
write: true
|
|
4
|
+
edit: false
|
|
5
|
+
sentry_find_organizations: true
|
|
6
|
+
sentry_find_projects: true
|
|
7
|
+
sentry_search_events: false
|
|
8
|
+
sentry_search_issues: true
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
You are the **Issue Fetcher & Prioritizer Agent**.
|
|
12
|
+
|
|
13
|
+
### TASK
|
|
14
|
+
Your goal is to identify the most critical Sentry issues for the requested projects and save them for processing.
|
|
15
|
+
|
|
16
|
+
### INSTRUCTIONS
|
|
17
|
+
1. **Identify Targets**:
|
|
18
|
+
* You have been provided a list of **Sentry Projects** in the prompt.
|
|
19
|
+
* Use your available Sentry tools to resolve the Organization and Project Slugs necessary to query them.
|
|
20
|
+
2. **Query Sentry**:
|
|
21
|
+
* Search for unresolved issues (`is:unresolved level:error`) in these projects.
|
|
22
|
+
* Limit: 20 issues per project.
|
|
23
|
+
3. **Prioritize**:
|
|
24
|
+
* Score issues (0-100) based on User Count, Event Frequency, and Age.
|
|
25
|
+
* Select the **Top 5** highest scoring issues.
|
|
26
|
+
4. **Output**:
|
|
27
|
+
* Read `state.json` (if exists) and exclude IDs in `processed_ids`.
|
|
28
|
+
* Write the filtered list to `issues.json` in the current directory.
|
|
29
|
+
|
|
30
|
+
### OUTPUT FORMAT (issues.json)
|
|
31
|
+
```json
|
|
32
|
+
[
|
|
33
|
+
{
|
|
34
|
+
"id": "SENTRY-123",
|
|
35
|
+
"title": "Error Title",
|
|
36
|
+
"organization": "org-slug",
|
|
37
|
+
"project": "project-slug",
|
|
38
|
+
"priority_score": 95,
|
|
39
|
+
"reason": "High impact"
|
|
40
|
+
}
|
|
41
|
+
]
|
|
42
|
+
```
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Implement code fixes and create Pull Requests
|
|
3
|
+
mode: primary
|
|
4
|
+
tools:
|
|
5
|
+
bash: true
|
|
6
|
+
write: true
|
|
7
|
+
edit: true
|
|
8
|
+
read: true
|
|
9
|
+
notify: true
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
You are the **Fixer Agent**.
|
|
13
|
+
|
|
14
|
+
### TASK
|
|
15
|
+
Implement the fix proposed in the RCA and submit a Pull Request.
|
|
16
|
+
|
|
17
|
+
### INPUT
|
|
18
|
+
* Read `rca.md` in the current directory.
|
|
19
|
+
* The current directory is `.gv-ai/runs/<timestamp>/<issue_id>/`.
|
|
20
|
+
|
|
21
|
+
### INSTRUCTIONS
|
|
22
|
+
1. **Preparation**:
|
|
23
|
+
* Read `rca.md` to understand the plan.
|
|
24
|
+
* **Git**: Ensure you are on the base branch (usually `main` or `master`). Pull latest.
|
|
25
|
+
* **Branch**: Create a new branch: `fix/sentry-<issue_id>-<timestamp>`.
|
|
26
|
+
2. **Implementation**:
|
|
27
|
+
* Use `edit` to modify the code.
|
|
28
|
+
* **Verify**: If possible, run a relevant test (or create a temporary test case) to ensure the fix works and doesn't break syntax.
|
|
29
|
+
* **Lint**: Run `npm run lint` (or equivalent) if available.
|
|
30
|
+
3. **Commit & PR**:
|
|
31
|
+
* `git commit -am "fix(sentry): Resolve <issue_id> - <brief description>"`
|
|
32
|
+
* `git push -u origin <branch_name>`
|
|
33
|
+
* Create PR using `gh pr create`:
|
|
34
|
+
* Title: `fix: Resolve Sentry Issue <issue_id>`
|
|
35
|
+
* Body: Include summary from `rca.md`.
|
|
36
|
+
4. **Notify**:
|
|
37
|
+
* Use `notify` tool: "✅ *Fix Implemented* for [Issue ID]\nPR: [PR URL]"
|
|
38
|
+
5. **Artifact**:
|
|
39
|
+
* Write `fix_result.json` with the PR URL and status.
|
|
40
|
+
|
|
41
|
+
### CONSTRAINTS
|
|
42
|
+
* Do NOT change unrelated code.
|
|
43
|
+
* If you cannot verify the fix (e.g., tests fail), abort and notify with a clear error message.
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
tools:
|
|
2
|
+
bash: true
|
|
3
|
+
write: true
|
|
4
|
+
edit: false
|
|
5
|
+
read: true
|
|
6
|
+
sentry_find_organizations: true
|
|
7
|
+
sentry_get_issue_details: true
|
|
8
|
+
sentry_get_issue_tag_values: true
|
|
9
|
+
sentry_analyze_issue_with_seer: true
|
|
10
|
+
notify: true
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
You are the **RCA (Root Cause Analysis) Agent**.
|
|
14
|
+
|
|
15
|
+
### TASK
|
|
16
|
+
Analyze the provided Sentry issue to determine the root cause and fixability.
|
|
17
|
+
|
|
18
|
+
### INPUT
|
|
19
|
+
* **Issue ID** (e.g., `SENTRY-123`) provided in the prompt.
|
|
20
|
+
* **Context**: You are running in `.gv-ai/runs/<timestamp>/<issue_id>/`.
|
|
21
|
+
|
|
22
|
+
### INSTRUCTIONS
|
|
23
|
+
1. **Analyze Issue**:
|
|
24
|
+
* Use your tools (`sentry_get_issue_details`, `sentry_analyze_issue_with_seer`) to inspect the issue.
|
|
25
|
+
* If you need the Organization slug and it wasn't provided, use your tools to find it.
|
|
26
|
+
* **Verify Code**: Check the local codebase (`grep`/`read`) to confirm the file and line number from the stack trace exist.
|
|
27
|
+
2. **Determine Root Cause**:
|
|
28
|
+
* Identify the logic error.
|
|
29
|
+
* Assess **Fixability** (High/Medium/Low).
|
|
30
|
+
3. **Report**:
|
|
31
|
+
* Write `rca.md` with your findings.
|
|
32
|
+
4. **Notify**:
|
|
33
|
+
* Send a Telegram summary using the `notify` tool.
|
|
34
|
+
|
|
35
|
+
### OUTPUT FORMAT (rca.md)
|
|
36
|
+
```markdown
|
|
37
|
+
# RCA: SENTRY-123
|
|
38
|
+
## Root Cause
|
|
39
|
+
...
|
|
40
|
+
## Evidence
|
|
41
|
+
...
|
|
42
|
+
## Proposed Fix
|
|
43
|
+
...
|
|
44
|
+
## Assessment
|
|
45
|
+
- **Confidence**: High/Medium/Low
|
|
46
|
+
- **Action**: Fix / Ticket / Skip
|
|
47
|
+
```
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
|
|
3
|
+
# Ensure we have a path (Cron often has limited path)
|
|
4
|
+
export PATH="/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:$PATH"
|
|
5
|
+
|
|
6
|
+
# Try to source user profile for NVM/Node/etc
|
|
7
|
+
if [ -f "$HOME/.bashrc" ]; then source "$HOME/.bashrc"; fi
|
|
8
|
+
if [ -f "$HOME/.zshrc" ]; then source "$HOME/.zshrc"; fi
|
|
9
|
+
if [ -f "$HOME/.profile" ]; then source "$HOME/.profile"; fi
|
|
10
|
+
|
|
11
|
+
# Directory Setup
|
|
12
|
+
# Resolve the directory where this script resides (.gv-ai/scripts)
|
|
13
|
+
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
|
|
14
|
+
# Base dir is parent of scripts (.gv-ai)
|
|
15
|
+
BASE_DIR="$(dirname "$SCRIPT_DIR")"
|
|
16
|
+
# Project Root is parent of .gv-ai
|
|
17
|
+
PROJECT_ROOT="$(dirname "$BASE_DIR")"
|
|
18
|
+
|
|
19
|
+
RUN_ID=$(date +%Y%m%d-%H%M%S)
|
|
20
|
+
RUN_DIR="$BASE_DIR/runs/$RUN_ID"
|
|
21
|
+
STATE_FILE="$BASE_DIR/state.json"
|
|
22
|
+
ENV_FILE="$BASE_DIR/env.json"
|
|
23
|
+
|
|
24
|
+
mkdir -p "$RUN_DIR"
|
|
25
|
+
|
|
26
|
+
# Load Environment Variables from env.json
|
|
27
|
+
if [ -f "$ENV_FILE" ]; then
|
|
28
|
+
# Simple parser for flat JSON string/number values
|
|
29
|
+
while IFS='=' read -r key value; do
|
|
30
|
+
# Remove quotes and comma
|
|
31
|
+
key=$(echo "$key" | tr -d '",')
|
|
32
|
+
value=$(echo "$value" | tr -d '",')
|
|
33
|
+
# Export if not empty
|
|
34
|
+
if [[ ! -z "$key" && "$key" != "{" && "$key" != "}" ]]; then
|
|
35
|
+
export "$key=$value"
|
|
36
|
+
fi
|
|
37
|
+
done < <(cat "$ENV_FILE" | grep ":")
|
|
38
|
+
fi
|
|
39
|
+
|
|
40
|
+
# Export Config Dir for OpenCode to find custom tools
|
|
41
|
+
export OPENCODE_CONFIG_DIR="$BASE_DIR"
|
|
42
|
+
|
|
43
|
+
echo "🚀 Starting Sentry Resolver Run: $RUN_ID"
|
|
44
|
+
echo "📂 Project Root: $PROJECT_ROOT"
|
|
45
|
+
|
|
46
|
+
# ---------------------------------------------------------
|
|
47
|
+
# Phase 1: Fetch Issues
|
|
48
|
+
# ---------------------------------------------------------
|
|
49
|
+
echo "🔍 Fetching issues..."
|
|
50
|
+
cd "$RUN_DIR" || exit 1
|
|
51
|
+
|
|
52
|
+
# Run opencode from Project Root so it can access project files if needed
|
|
53
|
+
cd "$PROJECT_ROOT" || exit 1
|
|
54
|
+
|
|
55
|
+
opencode run \
|
|
56
|
+
--agent gv-fetcher \
|
|
57
|
+
--prompt "Fetch critical issues for projects: ${SENTRY_PROJECTS}. Write output to $RUN_DIR/issues.json" \
|
|
58
|
+
> "$RUN_DIR/fetcher.log" 2>&1
|
|
59
|
+
|
|
60
|
+
if [ ! -f "$RUN_DIR/issues.json" ]; then
|
|
61
|
+
echo "⚠️ No issues.json created. Check $RUN_DIR/fetcher.log"
|
|
62
|
+
exit 0
|
|
63
|
+
fi
|
|
64
|
+
|
|
65
|
+
# ---------------------------------------------------------
|
|
66
|
+
# Phase 2: Process Loop
|
|
67
|
+
# ---------------------------------------------------------
|
|
68
|
+
# Use node to parse the issues.json because bash JSON parsing is painful
|
|
69
|
+
node -e "
|
|
70
|
+
const fs = require('fs');
|
|
71
|
+
const issues = JSON.parse(fs.readFileSync('$RUN_DIR/issues.json', 'utf8'));
|
|
72
|
+
issues.forEach(issue => console.log(issue.id));
|
|
73
|
+
" > "$RUN_DIR/issue_list.txt"
|
|
74
|
+
|
|
75
|
+
while read -r ISSUE_ID; do
|
|
76
|
+
if [ -z "$ISSUE_ID" ]; then continue; fi
|
|
77
|
+
|
|
78
|
+
echo "⚙️ Processing Issue: $ISSUE_ID"
|
|
79
|
+
ISSUE_DIR="$RUN_DIR/$ISSUE_ID"
|
|
80
|
+
mkdir -p "$ISSUE_DIR"
|
|
81
|
+
|
|
82
|
+
# RCA Phase
|
|
83
|
+
echo " - Running RCA..."
|
|
84
|
+
opencode run \
|
|
85
|
+
--agent gv-rca \
|
|
86
|
+
--prompt "Perform RCA for $ISSUE_ID. Working directory: $ISSUE_DIR" \
|
|
87
|
+
> "$ISSUE_DIR/rca.log" 2>&1
|
|
88
|
+
|
|
89
|
+
if [ ! -f "$ISSUE_DIR/rca.md" ]; then
|
|
90
|
+
echo " ⚠️ RCA failed to produce report."
|
|
91
|
+
continue
|
|
92
|
+
fi
|
|
93
|
+
|
|
94
|
+
# Decision Check
|
|
95
|
+
# We look for 'Action: Fix' in the RCA markdown
|
|
96
|
+
ACTION=$(grep -i "Action:" "$ISSUE_DIR/rca.md" | head -n 1)
|
|
97
|
+
|
|
98
|
+
if [[ "$ACTION" == *"Fix"* ]]; then
|
|
99
|
+
echo " - Decision: FIX. Running Fixer..."
|
|
100
|
+
opencode run \
|
|
101
|
+
--agent gv-fixer \
|
|
102
|
+
--prompt "Fix $ISSUE_ID. RCA is at $ISSUE_DIR/rca.md" \
|
|
103
|
+
> "$ISSUE_DIR/fixer.log" 2>&1
|
|
104
|
+
else
|
|
105
|
+
echo " - Decision: SKIP/TICKET ($ACTION)"
|
|
106
|
+
fi
|
|
107
|
+
|
|
108
|
+
# Update State (Append to processed list)
|
|
109
|
+
# In a real impl, we'd read/write JSON properly. For now, we assume fetcher handles filtering.
|
|
110
|
+
# But we should append to a processed log.
|
|
111
|
+
echo "$ISSUE_ID processed at $(date)" >> "$BASE_DIR/processed_log.txt"
|
|
112
|
+
|
|
113
|
+
done < "$RUN_DIR/issue_list.txt"
|
|
114
|
+
|
|
115
|
+
echo "✅ Run Complete."
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { tool } from "@opencode-ai/plugin";
|
|
2
|
+
|
|
3
|
+
export default tool({
|
|
4
|
+
description: "Send a notification via Telegram",
|
|
5
|
+
args: {
|
|
6
|
+
message: tool.schema.string().describe("The message to send"),
|
|
7
|
+
},
|
|
8
|
+
async execute(args) {
|
|
9
|
+
const fs = await import("fs/promises");
|
|
10
|
+
const path = await import("path");
|
|
11
|
+
|
|
12
|
+
// Load secrets from .gv-ai/env.json (this file is in .gv-ai/tool/)
|
|
13
|
+
const envPath = path.join(__dirname, "..", "env.json");
|
|
14
|
+
|
|
15
|
+
let config;
|
|
16
|
+
try {
|
|
17
|
+
const envContent = await fs.readFile(envPath, "utf-8");
|
|
18
|
+
config = JSON.parse(envContent);
|
|
19
|
+
} catch {
|
|
20
|
+
return "Error: Could not load .gv-ai/env.json";
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const { TELEGRAM_BOT_TOKEN, TELEGRAM_CHAT_ID } = config;
|
|
24
|
+
if (!TELEGRAM_BOT_TOKEN || !TELEGRAM_CHAT_ID) {
|
|
25
|
+
return "Error: Telegram credentials missing in env.json";
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const text = `🤖 *Tal Auto-Fixer*\n\n${args.message}`;
|
|
29
|
+
|
|
30
|
+
try {
|
|
31
|
+
const res = await fetch(
|
|
32
|
+
`https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/sendMessage`,
|
|
33
|
+
{
|
|
34
|
+
method: "POST",
|
|
35
|
+
headers: { "Content-Type": "application/json" },
|
|
36
|
+
body: JSON.stringify({
|
|
37
|
+
chat_id: TELEGRAM_CHAT_ID,
|
|
38
|
+
text,
|
|
39
|
+
parse_mode: "Markdown",
|
|
40
|
+
}),
|
|
41
|
+
}
|
|
42
|
+
);
|
|
43
|
+
const result = await res.json();
|
|
44
|
+
return result.ok
|
|
45
|
+
? "Notification sent."
|
|
46
|
+
: `Telegram Error: ${result.description}`;
|
|
47
|
+
} catch (e: any) {
|
|
48
|
+
return `Network Error: ${e.message}`;
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
});
|
package/package.json
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gv-ai",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "Internal CLI tool for GV team - Linear ticket enforcement",
|
|
3
|
+
"version": "1.1.0",
|
|
4
|
+
"description": "Internal CLI tool for GV team - Linear ticket enforcement and automated Sentry issue resolution",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
7
|
"gv-ai": "./dist/cli.js"
|
|
8
8
|
},
|
|
9
9
|
"scripts": {
|
|
10
|
-
"build": "bun build src/cli.ts --outdir dist --target node --format esm && bun run build:types",
|
|
10
|
+
"build": "bun build src/cli.ts --outdir dist --target node --format esm && cp -r src/templates dist/ && bun run build:types",
|
|
11
11
|
"build:types": "tsc --emitDeclarationOnly",
|
|
12
12
|
"dev": "bun run src/cli.ts",
|
|
13
13
|
"start": "bun run dist/cli.js",
|