supipowers 0.2.7 → 0.4.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/package.json +21 -6
- package/skills/debugging/SKILL.md +54 -15
- package/skills/fix-pr/SKILL.md +99 -0
- package/skills/planning/SKILL.md +70 -10
- package/skills/receiving-code-review/SKILL.md +87 -0
- package/skills/tdd/SKILL.md +83 -0
- package/skills/verification/SKILL.md +54 -0
- package/src/commands/fix-pr.ts +324 -0
- package/src/commands/plan.ts +96 -31
- package/src/commands/qa.ts +150 -29
- package/src/commands/release.ts +1 -1
- package/src/commands/review.ts +2 -2
- package/src/commands/run.ts +52 -2
- package/src/commands/supi.ts +1 -0
- package/src/commands/update.ts +2 -2
- package/src/discipline/debugging.ts +57 -0
- package/src/discipline/receiving-review.ts +65 -0
- package/src/discipline/tdd.ts +77 -0
- package/src/discipline/verification.ts +68 -0
- package/src/fix-pr/config.ts +36 -0
- package/src/fix-pr/prompt-builder.ts +201 -0
- package/src/fix-pr/scripts/diff-comments.sh +33 -0
- package/src/fix-pr/scripts/fetch-pr-comments.sh +25 -0
- package/src/fix-pr/scripts/trigger-review.sh +36 -0
- package/src/fix-pr/scripts/wait-and-check.sh +37 -0
- package/src/fix-pr/types.ts +71 -0
- package/src/git/branch-finish.ts +101 -0
- package/src/git/worktree.ts +119 -0
- package/src/index.ts +13 -2
- package/src/lsp/detector.ts +2 -2
- package/src/orchestrator/agent-prompts.ts +282 -0
- package/src/orchestrator/dispatcher.ts +150 -1
- package/src/orchestrator/prompts.ts +17 -31
- package/src/planning/plan-reviewer.ts +49 -0
- package/src/planning/plan-writer-prompt.ts +173 -0
- package/src/planning/prompt-builder.ts +178 -0
- package/src/planning/spec-reviewer.ts +43 -0
- package/src/qa/phases/discovery.ts +34 -0
- package/src/qa/phases/execution.ts +65 -0
- package/src/qa/phases/matrix.ts +41 -0
- package/src/qa/phases/reporting.ts +71 -0
- package/src/qa/session.ts +104 -0
- package/src/storage/fix-pr-sessions.ts +59 -0
- package/src/storage/qa-sessions.ts +83 -0
- package/src/storage/specs.ts +36 -0
- package/src/types.ts +70 -0
- package/src/visual/companion.ts +115 -0
- package/src/visual/prompt-instructions.ts +102 -0
- package/src/visual/scripts/frame-template.html +201 -0
- package/src/visual/scripts/helper.js +88 -0
- package/src/visual/scripts/index.js +148 -0
- package/src/visual/scripts/package.json +10 -0
- package/src/visual/scripts/start-server.sh +98 -0
- package/src/visual/scripts/stop-server.sh +21 -0
- package/src/visual/types.ts +16 -0
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Compares two JSONL comment snapshots, outputs only new/changed comments
|
|
3
|
+
# Usage: diff-comments.sh <prev_snapshot> <new_snapshot>
|
|
4
|
+
# Exit 0 if new comments found, exit 1 if identical
|
|
5
|
+
set -euo pipefail
|
|
6
|
+
|
|
7
|
+
PREV="$1"
|
|
8
|
+
NEW="$2"
|
|
9
|
+
|
|
10
|
+
# If no previous snapshot, all comments are new
|
|
11
|
+
if [[ ! -f "$PREV" ]]; then
|
|
12
|
+
cat "$NEW"
|
|
13
|
+
exit 0
|
|
14
|
+
fi
|
|
15
|
+
|
|
16
|
+
# Build fingerprint: id + updatedAt for each comment
|
|
17
|
+
prev_fingerprints=$(jq -r '[.id, .updatedAt] | @tsv' "$PREV" 2>/dev/null | sort)
|
|
18
|
+
new_fingerprints=$(jq -r '[.id, .updatedAt] | @tsv' "$NEW" 2>/dev/null | sort)
|
|
19
|
+
|
|
20
|
+
# Find IDs that are new or changed
|
|
21
|
+
new_ids=$(comm -13 <(echo "$prev_fingerprints") <(echo "$new_fingerprints") | cut -f1)
|
|
22
|
+
|
|
23
|
+
if [[ -z "$new_ids" ]]; then
|
|
24
|
+
exit 1
|
|
25
|
+
fi
|
|
26
|
+
|
|
27
|
+
# Output the full comment objects for new/changed IDs
|
|
28
|
+
while IFS= read -r id; do
|
|
29
|
+
[[ -z "$id" ]] && continue
|
|
30
|
+
jq -c "select(.id == $id)" "$NEW"
|
|
31
|
+
done <<< "$new_ids"
|
|
32
|
+
|
|
33
|
+
exit 0
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Fetches all review comments for a PR, outputs JSONL
|
|
3
|
+
# Usage: fetch-pr-comments.sh <owner/repo> <pr_number> <output_file>
|
|
4
|
+
set -euo pipefail
|
|
5
|
+
|
|
6
|
+
REPO="$1"
|
|
7
|
+
PR="$2"
|
|
8
|
+
OUTPUT="$3"
|
|
9
|
+
|
|
10
|
+
# Ensure output directory exists
|
|
11
|
+
mkdir -p "$(dirname "$OUTPUT")"
|
|
12
|
+
|
|
13
|
+
# Fetch inline review comments (code-level)
|
|
14
|
+
gh api --paginate "repos/${REPO}/pulls/${PR}/comments" \
|
|
15
|
+
--jq '.[] | {id, path, line: .line, body, user: .user.login, createdAt: .created_at, updatedAt: .updated_at, inReplyToId: .in_reply_to_id, diffHunk: .diff_hunk, state: "COMMENTED"}' \
|
|
16
|
+
> "$OUTPUT" 2>/dev/null || true
|
|
17
|
+
|
|
18
|
+
# Fetch review-level comments (top-level reviews with body text)
|
|
19
|
+
gh api --paginate "repos/${REPO}/pulls/${PR}/reviews" \
|
|
20
|
+
--jq '.[] | select(.body != null and .body != "") | {id, path: null, line: null, body, user: .user.login, createdAt: .submitted_at, updatedAt: .submitted_at, inReplyToId: null, diffHunk: null, state}' \
|
|
21
|
+
>> "$OUTPUT" 2>/dev/null || true
|
|
22
|
+
|
|
23
|
+
# Output summary to stderr for caller
|
|
24
|
+
TOTAL=$(wc -l < "$OUTPUT" | tr -d ' ')
|
|
25
|
+
echo "{\"total\": ${TOTAL}}" >&2
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Triggers automated reviewer to re-review a PR
|
|
3
|
+
# Usage: trigger-review.sh <owner/repo> <pr_number> <reviewer_type> <trigger_method>
|
|
4
|
+
set -euo pipefail
|
|
5
|
+
|
|
6
|
+
REPO="$1"
|
|
7
|
+
PR="$2"
|
|
8
|
+
REVIEWER="$3"
|
|
9
|
+
METHOD="${4:-}"
|
|
10
|
+
|
|
11
|
+
case "$REVIEWER" in
|
|
12
|
+
coderabbit)
|
|
13
|
+
gh api "repos/${REPO}/issues/${PR}/comments" -f body="$METHOD" >/dev/null 2>&1
|
|
14
|
+
echo '{"triggered": true, "reviewer": "coderabbit"}'
|
|
15
|
+
;;
|
|
16
|
+
copilot)
|
|
17
|
+
if [[ -n "$METHOD" ]]; then
|
|
18
|
+
gh api "repos/${REPO}/issues/${PR}/comments" -f body="$METHOD" >/dev/null 2>&1
|
|
19
|
+
else
|
|
20
|
+
gh api "repos/${REPO}/pulls/${PR}/requested_reviewers" \
|
|
21
|
+
--method POST -f "reviewers[]=copilot" >/dev/null 2>&1 || true
|
|
22
|
+
fi
|
|
23
|
+
echo '{"triggered": true, "reviewer": "copilot"}'
|
|
24
|
+
;;
|
|
25
|
+
gemini)
|
|
26
|
+
gh api "repos/${REPO}/issues/${PR}/comments" -f body="$METHOD" >/dev/null 2>&1
|
|
27
|
+
echo '{"triggered": true, "reviewer": "gemini"}'
|
|
28
|
+
;;
|
|
29
|
+
none)
|
|
30
|
+
echo '{"triggered": false, "reviewer": "none"}'
|
|
31
|
+
;;
|
|
32
|
+
*)
|
|
33
|
+
echo '{"triggered": false, "error": "unknown reviewer type: '"$REVIEWER"'"}'
|
|
34
|
+
exit 1
|
|
35
|
+
;;
|
|
36
|
+
esac
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Waits for delay, fetches new PR comments, diffs against previous snapshot
|
|
3
|
+
# Usage: wait-and-check.sh <session_dir> <delay_seconds> <iteration> <owner/repo> <pr_number>
|
|
4
|
+
# Output: new comment lines + JSON summary on last line
|
|
5
|
+
set -euo pipefail
|
|
6
|
+
|
|
7
|
+
SESSION_DIR="$1"
|
|
8
|
+
DELAY="$2"
|
|
9
|
+
ITERATION="$3"
|
|
10
|
+
REPO="$4"
|
|
11
|
+
PR="$5"
|
|
12
|
+
|
|
13
|
+
SNAPSHOTS_DIR="${SESSION_DIR}/snapshots"
|
|
14
|
+
PREV_ITERATION=$((ITERATION - 1))
|
|
15
|
+
PREV_SNAPSHOT="${SNAPSHOTS_DIR}/comments-${PREV_ITERATION}.jsonl"
|
|
16
|
+
NEW_SNAPSHOT="${SNAPSHOTS_DIR}/comments-${ITERATION}.jsonl"
|
|
17
|
+
|
|
18
|
+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
19
|
+
|
|
20
|
+
# Wait for reviewer to process
|
|
21
|
+
echo "Waiting ${DELAY}s for reviewer to process changes..." >&2
|
|
22
|
+
sleep "$DELAY"
|
|
23
|
+
|
|
24
|
+
# Fetch new comments
|
|
25
|
+
echo "Fetching PR comments (iteration ${ITERATION})..." >&2
|
|
26
|
+
bash "${SCRIPT_DIR}/fetch-pr-comments.sh" "$REPO" "$PR" "$NEW_SNAPSHOT"
|
|
27
|
+
|
|
28
|
+
# Diff against previous
|
|
29
|
+
DIFF_OUTPUT=$(bash "${SCRIPT_DIR}/diff-comments.sh" "$PREV_SNAPSHOT" "$NEW_SNAPSHOT" 2>/dev/null) || true
|
|
30
|
+
|
|
31
|
+
if [[ -n "$DIFF_OUTPUT" ]]; then
|
|
32
|
+
DIFF_COUNT=$(echo "$DIFF_OUTPUT" | wc -l | tr -d ' ')
|
|
33
|
+
echo "$DIFF_OUTPUT"
|
|
34
|
+
echo "{\"hasNewComments\": true, \"count\": ${DIFF_COUNT}, \"iteration\": ${ITERATION}}"
|
|
35
|
+
else
|
|
36
|
+
echo "{\"hasNewComments\": false, \"count\": 0, \"iteration\": ${ITERATION}}"
|
|
37
|
+
fi
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/** Supported automated PR reviewers */
|
|
2
|
+
export type ReviewerType = "coderabbit" | "copilot" | "gemini" | "none";
|
|
3
|
+
|
|
4
|
+
/** How to handle comment replies */
|
|
5
|
+
export type CommentReplyPolicy = "answer-all" | "answer-selective" | "no-answer";
|
|
6
|
+
|
|
7
|
+
/** Model preference for a specific role */
|
|
8
|
+
export interface ModelPref {
|
|
9
|
+
provider: string;
|
|
10
|
+
model: string;
|
|
11
|
+
tier: "low" | "high";
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/** Per-repo fix-pr configuration */
|
|
15
|
+
export interface FixPrConfig {
|
|
16
|
+
reviewer: {
|
|
17
|
+
type: ReviewerType;
|
|
18
|
+
triggerMethod: string | null;
|
|
19
|
+
};
|
|
20
|
+
commentPolicy: CommentReplyPolicy;
|
|
21
|
+
loop: {
|
|
22
|
+
delaySeconds: number;
|
|
23
|
+
maxIterations: number;
|
|
24
|
+
};
|
|
25
|
+
models: {
|
|
26
|
+
orchestrator: ModelPref;
|
|
27
|
+
planner: ModelPref;
|
|
28
|
+
fixer: ModelPref;
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** A PR review comment from GitHub API */
|
|
33
|
+
export interface PrComment {
|
|
34
|
+
id: number;
|
|
35
|
+
path: string | null;
|
|
36
|
+
line: number | null;
|
|
37
|
+
body: string;
|
|
38
|
+
user: string;
|
|
39
|
+
createdAt: string;
|
|
40
|
+
updatedAt: string;
|
|
41
|
+
inReplyToId: number | null;
|
|
42
|
+
diffHunk: string | null;
|
|
43
|
+
state: string;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** Assessment verdict for a single comment */
|
|
47
|
+
export type CommentVerdict = "accept" | "reject" | "investigate";
|
|
48
|
+
|
|
49
|
+
/** A group of related comments to fix together */
|
|
50
|
+
export interface FixGroup {
|
|
51
|
+
id: string;
|
|
52
|
+
commentIds: number[];
|
|
53
|
+
files: string[];
|
|
54
|
+
description: string;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** Session status */
|
|
58
|
+
export type FixPrSessionStatus = "running" | "completed" | "failed";
|
|
59
|
+
|
|
60
|
+
/** Session ledger for a fix-pr run */
|
|
61
|
+
export interface FixPrSessionLedger {
|
|
62
|
+
id: string;
|
|
63
|
+
createdAt: string;
|
|
64
|
+
updatedAt: string;
|
|
65
|
+
prNumber: number;
|
|
66
|
+
repo: string;
|
|
67
|
+
status: FixPrSessionStatus;
|
|
68
|
+
iteration: number;
|
|
69
|
+
config: FixPrConfig;
|
|
70
|
+
commentsProcessed: number[];
|
|
71
|
+
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
export interface FinishOption {
|
|
2
|
+
id: "merge" | "pr" | "keep" | "discard";
|
|
3
|
+
label: string;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
/** The 4 structured options for finishing a branch */
|
|
7
|
+
export const FINISH_OPTIONS: FinishOption[] = [
|
|
8
|
+
{ id: "merge", label: "Merge back to base branch locally" },
|
|
9
|
+
{ id: "pr", label: "Push and create a Pull Request" },
|
|
10
|
+
{ id: "keep", label: "Keep the branch as-is (handle later)" },
|
|
11
|
+
{ id: "discard", label: "Discard this work" },
|
|
12
|
+
];
|
|
13
|
+
|
|
14
|
+
export interface BranchFinishPromptOptions {
|
|
15
|
+
branchName: string;
|
|
16
|
+
baseBranch: string;
|
|
17
|
+
worktreePath?: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Build the prompt that guides the agent through finishing a development branch.
|
|
22
|
+
* Follows superpowers' finishing-a-development-branch skill:
|
|
23
|
+
* - Verify tests pass first
|
|
24
|
+
* - Present exactly 4 options
|
|
25
|
+
* - Execute chosen option
|
|
26
|
+
* - Clean up worktree (conditional)
|
|
27
|
+
*/
|
|
28
|
+
export function buildBranchFinishPrompt(options: BranchFinishPromptOptions): string {
|
|
29
|
+
const { branchName, baseBranch, worktreePath } = options;
|
|
30
|
+
|
|
31
|
+
const sections: string[] = [
|
|
32
|
+
"## Finish Development Branch",
|
|
33
|
+
"",
|
|
34
|
+
`Branch: \`${branchName}\` (base: \`${baseBranch}\`)`,
|
|
35
|
+
"",
|
|
36
|
+
"### Step 1: Verify tests pass",
|
|
37
|
+
"",
|
|
38
|
+
"Run the full test suite. All tests must pass before proceeding.",
|
|
39
|
+
"If tests fail, fix them first — do not offer options until green.",
|
|
40
|
+
"",
|
|
41
|
+
"### Step 2: Present options",
|
|
42
|
+
"",
|
|
43
|
+
"Ask the user:",
|
|
44
|
+
"",
|
|
45
|
+
"> Implementation complete. What would you like to do?",
|
|
46
|
+
">",
|
|
47
|
+
`> 1. Merge back to \`${baseBranch}\` locally`,
|
|
48
|
+
"> 2. Push and create a Pull Request",
|
|
49
|
+
"> 3. Keep the branch as-is (handle later)",
|
|
50
|
+
"> 4. Discard this work",
|
|
51
|
+
"",
|
|
52
|
+
"### Option 1: Merge locally",
|
|
53
|
+
"",
|
|
54
|
+
"```bash",
|
|
55
|
+
`git checkout ${baseBranch}`,
|
|
56
|
+
"git pull",
|
|
57
|
+
`git merge ${branchName}`,
|
|
58
|
+
"# Verify tests pass on merged result",
|
|
59
|
+
`git branch -d ${branchName}`,
|
|
60
|
+
"```",
|
|
61
|
+
"",
|
|
62
|
+
"### Option 2: Push and create Pull Request",
|
|
63
|
+
"",
|
|
64
|
+
"```bash",
|
|
65
|
+
`git push -u origin ${branchName}`,
|
|
66
|
+
`gh pr create --title "<title>" --body "<summary>"`,
|
|
67
|
+
"```",
|
|
68
|
+
"",
|
|
69
|
+
"### Option 3: Keep as-is",
|
|
70
|
+
"",
|
|
71
|
+
`Report: "Keeping branch ${branchName}."`,
|
|
72
|
+
"Do NOT clean up worktree.",
|
|
73
|
+
"",
|
|
74
|
+
"### Option 4: Discard",
|
|
75
|
+
"",
|
|
76
|
+
"**Require explicit confirm before deleting.** Show what will be lost:",
|
|
77
|
+
"",
|
|
78
|
+
"```bash",
|
|
79
|
+
`git checkout ${baseBranch}`,
|
|
80
|
+
`git branch -D ${branchName}`,
|
|
81
|
+
"```",
|
|
82
|
+
];
|
|
83
|
+
|
|
84
|
+
if (worktreePath) {
|
|
85
|
+
sections.push(
|
|
86
|
+
"",
|
|
87
|
+
"### Worktree cleanup",
|
|
88
|
+
"",
|
|
89
|
+
`Worktree at: \`${worktreePath}\``,
|
|
90
|
+
"",
|
|
91
|
+
"- **Options 1 and 4:** Clean up the worktree:",
|
|
92
|
+
" ```bash",
|
|
93
|
+
` git worktree remove ${worktreePath}`,
|
|
94
|
+
" ```",
|
|
95
|
+
"- **Option 2:** Keep worktree (PR may need updates)",
|
|
96
|
+
"- **Option 3:** Keep worktree",
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return sections.join("\n");
|
|
101
|
+
}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import * as fs from "node:fs";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
|
|
4
|
+
export interface ProjectSetup {
|
|
5
|
+
type: "node" | "rust" | "python" | "go" | "unknown";
|
|
6
|
+
installCommand: string | null;
|
|
7
|
+
testCommand: string | null;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Detect existing worktree directory following priority:
|
|
12
|
+
* .worktrees > worktrees > null
|
|
13
|
+
*/
|
|
14
|
+
export function detectWorktreeDir(cwd: string): string | null {
|
|
15
|
+
const dotWorktrees = path.join(cwd, ".worktrees");
|
|
16
|
+
if (fs.existsSync(dotWorktrees)) return dotWorktrees;
|
|
17
|
+
|
|
18
|
+
const worktrees = path.join(cwd, "worktrees");
|
|
19
|
+
if (fs.existsSync(worktrees)) return worktrees;
|
|
20
|
+
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Auto-detect project type and setup commands from project files.
|
|
26
|
+
*/
|
|
27
|
+
export function detectProjectSetup(cwd: string): ProjectSetup {
|
|
28
|
+
if (fs.existsSync(path.join(cwd, "package.json"))) {
|
|
29
|
+
return { type: "node", installCommand: "npm install", testCommand: "npm test" };
|
|
30
|
+
}
|
|
31
|
+
if (fs.existsSync(path.join(cwd, "Cargo.toml"))) {
|
|
32
|
+
return { type: "rust", installCommand: "cargo build", testCommand: "cargo test" };
|
|
33
|
+
}
|
|
34
|
+
if (fs.existsSync(path.join(cwd, "requirements.txt"))) {
|
|
35
|
+
return { type: "python", installCommand: "pip install -r requirements.txt", testCommand: "pytest" };
|
|
36
|
+
}
|
|
37
|
+
if (fs.existsSync(path.join(cwd, "pyproject.toml"))) {
|
|
38
|
+
return { type: "python", installCommand: "poetry install", testCommand: "pytest" };
|
|
39
|
+
}
|
|
40
|
+
if (fs.existsSync(path.join(cwd, "go.mod"))) {
|
|
41
|
+
return { type: "go", installCommand: "go mod download", testCommand: "go test ./..." };
|
|
42
|
+
}
|
|
43
|
+
return { type: "unknown", installCommand: null, testCommand: null };
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export interface WorktreePromptOptions {
|
|
47
|
+
branchName: string;
|
|
48
|
+
cwd: string;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Build the prompt that guides the agent through creating an isolated git worktree.
|
|
53
|
+
* Follows superpowers' using-git-worktrees skill:
|
|
54
|
+
* - Smart directory selection (.worktrees > worktrees > ask)
|
|
55
|
+
* - .gitignore verification
|
|
56
|
+
* - Project setup detection
|
|
57
|
+
* - Baseline test verification
|
|
58
|
+
*/
|
|
59
|
+
export function buildWorktreePrompt(options: WorktreePromptOptions): string {
|
|
60
|
+
const { branchName, cwd } = options;
|
|
61
|
+
|
|
62
|
+
return [
|
|
63
|
+
"## Set Up Isolated Worktree",
|
|
64
|
+
"",
|
|
65
|
+
`Create an isolated workspace for branch \`${branchName}\`.`,
|
|
66
|
+
"",
|
|
67
|
+
"### Step 1: Select directory",
|
|
68
|
+
"",
|
|
69
|
+
"Check in priority order:",
|
|
70
|
+
`1. \`${cwd}/.worktrees/\` — if it exists, use it`,
|
|
71
|
+
`2. \`${cwd}/worktrees/\` — if it exists, use it`,
|
|
72
|
+
"3. Check CLAUDE.md for worktree directory preference",
|
|
73
|
+
"4. If none found, ask the user:",
|
|
74
|
+
' - `.worktrees/` (project-local, hidden)',
|
|
75
|
+
' - `~/.config/supipowers/worktrees/<project>/` (global location)',
|
|
76
|
+
"",
|
|
77
|
+
"### Step 2: Verify gitignore",
|
|
78
|
+
"",
|
|
79
|
+
"For project-local directories, verify the directory is ignored:",
|
|
80
|
+
"",
|
|
81
|
+
"```bash",
|
|
82
|
+
"git check-ignore -q .worktrees 2>/dev/null || git check-ignore -q worktrees 2>/dev/null",
|
|
83
|
+
"```",
|
|
84
|
+
"",
|
|
85
|
+
"If NOT ignored, add it to .gitignore and commit before proceeding.",
|
|
86
|
+
"",
|
|
87
|
+
"### Step 3: Create worktree",
|
|
88
|
+
"",
|
|
89
|
+
"```bash",
|
|
90
|
+
`git worktree add <dir>/${branchName} -b ${branchName}`,
|
|
91
|
+
`cd <dir>/${branchName}`,
|
|
92
|
+
"```",
|
|
93
|
+
"",
|
|
94
|
+
"### Step 4: Project setup",
|
|
95
|
+
"",
|
|
96
|
+
"Auto-detect and run appropriate setup:",
|
|
97
|
+
"",
|
|
98
|
+
"| File | Command |",
|
|
99
|
+
"|------|---------|",
|
|
100
|
+
"| `package.json` | `npm install` |",
|
|
101
|
+
"| `Cargo.toml` | `cargo build` |",
|
|
102
|
+
"| `requirements.txt` | `pip install -r requirements.txt` |",
|
|
103
|
+
"| `pyproject.toml` | `poetry install` |",
|
|
104
|
+
"| `go.mod` | `go mod download` |",
|
|
105
|
+
"",
|
|
106
|
+
"### Step 5: Verify baseline",
|
|
107
|
+
"",
|
|
108
|
+
"Run the test suite to verify a clean baseline before starting work.",
|
|
109
|
+
"If tests fail, report failures and ask whether to proceed or investigate.",
|
|
110
|
+
"",
|
|
111
|
+
"### Step 6: Report",
|
|
112
|
+
"",
|
|
113
|
+
"```",
|
|
114
|
+
"Worktree ready at <full-path>",
|
|
115
|
+
"Tests passing (<N> tests, 0 failures)",
|
|
116
|
+
`Ready to implement ${branchName}`,
|
|
117
|
+
"```",
|
|
118
|
+
].join("\n");
|
|
119
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -5,12 +5,14 @@ import { homedir, tmpdir } from "node:os";
|
|
|
5
5
|
import { registerSupiCommand, handleSupi } from "./commands/supi.js";
|
|
6
6
|
import { registerConfigCommand, handleConfig } from "./commands/config.js";
|
|
7
7
|
import { registerStatusCommand, handleStatus } from "./commands/status.js";
|
|
8
|
-
import { registerPlanCommand } from "./commands/plan.js";
|
|
8
|
+
import { registerPlanCommand, getActiveVisualSessionDir, setActiveVisualSessionDir } from "./commands/plan.js";
|
|
9
|
+
import { getScriptsDir } from "./visual/companion.js";
|
|
9
10
|
import { registerRunCommand } from "./commands/run.js";
|
|
10
11
|
import { registerReviewCommand } from "./commands/review.js";
|
|
11
12
|
import { registerQaCommand } from "./commands/qa.js";
|
|
12
13
|
import { registerReleaseCommand } from "./commands/release.js";
|
|
13
14
|
import { registerUpdateCommand, handleUpdate } from "./commands/update.js";
|
|
15
|
+
import { registerFixPrCommand } from "./commands/fix-pr.js";
|
|
14
16
|
|
|
15
17
|
// TUI-only commands — intercepted at the input level to prevent
|
|
16
18
|
// message submission and "Working..." indicator
|
|
@@ -42,6 +44,7 @@ export default function supipowers(pi: ExtensionAPI): void {
|
|
|
42
44
|
registerQaCommand(pi);
|
|
43
45
|
registerReleaseCommand(pi);
|
|
44
46
|
registerUpdateCommand(pi);
|
|
47
|
+
registerFixPrCommand(pi);
|
|
45
48
|
|
|
46
49
|
// Intercept TUI-only commands at the input level — this runs BEFORE
|
|
47
50
|
// message submission, so no chat message appears and no "Working..." indicator
|
|
@@ -61,13 +64,21 @@ export default function supipowers(pi: ExtensionAPI): void {
|
|
|
61
64
|
|
|
62
65
|
// Session start
|
|
63
66
|
pi.on("session_start", async (_event, ctx) => {
|
|
67
|
+
// Clean up any leftover visual companion from a previous session
|
|
68
|
+
const previousVisualDir = getActiveVisualSessionDir();
|
|
69
|
+
if (previousVisualDir) {
|
|
70
|
+
const stopScript = join(getScriptsDir(), "stop-server.sh");
|
|
71
|
+
pi.exec("bash", [stopScript, previousVisualDir], { cwd: getScriptsDir() }).catch(() => {});
|
|
72
|
+
setActiveVisualSessionDir(null);
|
|
73
|
+
}
|
|
74
|
+
|
|
64
75
|
// Check for updates in the background
|
|
65
76
|
const currentVersion = getInstalledVersion();
|
|
66
77
|
if (!currentVersion) return;
|
|
67
78
|
|
|
68
79
|
pi.exec("npm", ["view", "supipowers", "version"], { cwd: tmpdir() })
|
|
69
80
|
.then((result) => {
|
|
70
|
-
if (result.
|
|
81
|
+
if (result.code !== 0) return;
|
|
71
82
|
const latest = result.stdout.trim();
|
|
72
83
|
if (latest && latest !== currentVersion) {
|
|
73
84
|
ctx.ui.notify(
|
package/src/lsp/detector.ts
CHANGED
|
@@ -10,13 +10,13 @@ export interface LspServerStatus {
|
|
|
10
10
|
* Check which LSP servers are installed by looking for their binaries.
|
|
11
11
|
*/
|
|
12
12
|
export async function checkInstalledServers(
|
|
13
|
-
exec: (cmd: string, args: string[]) => Promise<{ stdout: string;
|
|
13
|
+
exec: (cmd: string, args: string[]) => Promise<{ stdout: string; code: number }>
|
|
14
14
|
): Promise<LspServerStatus[]> {
|
|
15
15
|
const results: LspServerStatus[] = [];
|
|
16
16
|
for (const server of LSP_SERVERS) {
|
|
17
17
|
try {
|
|
18
18
|
const result = await exec("which", [server.server]);
|
|
19
|
-
results.push({ server, installed: result.
|
|
19
|
+
results.push({ server, installed: result.code === 0 });
|
|
20
20
|
} catch {
|
|
21
21
|
results.push({ server, installed: false });
|
|
22
22
|
}
|