Skip to content

Commit e4ac490

Browse files
committed
Sync skills and rules from ~/.cursor
1 parent 8726edd commit e4ac490

File tree

10 files changed

+160
-11
lines changed

10 files changed

+160
-11
lines changed

.cursor/rules/typescript-standards.mdc

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,17 @@ Exception: Empty handlers are acceptable ONLY when the rejection is an expected
119119
✅ `const impact = div(sub(from, to), from, 8)`
120120
</standard>
121121

122+
<standard id="memoize-derived-collections">When deriving arrays or objects from props/state (e.g. `Object.values()`, `Object.keys()`, `.filter()`, `.map()`), wrap in `React.useMemo` if the result is used in a dependency array or passed as a prop. Bare derivations create new references every render.
123+
❌ `const wallets = Object.values(currencyWallets)` (used in effect deps)
124+
✅ `const wallets = React.useMemo(() => Object.values(currencyWallets), [currencyWallets])`
125+
</standard>
126+
127+
<standard id="guard-on-success-not-existence">When guarding against re-fetching with nullable map lookups, check for the success payload specifically — not just entry existence. Storing error results as non-null entries permanently blocks retry if the guard only checks `== null`.
128+
❌ `if (resultMap[id] == null) fetchData(id)` (error entries block retry)
129+
✅ `if (resultMap[id]?.data == null) fetchData(id)` (only skip when data is present)
130+
Exception: Auto-load effects where infinite retry on persistent failure is undesirable — keep `== null` there and allow retry only via explicit user action.
131+
</standard>
132+
122133
</standards>
123134

124135
<lint-fix-patterns description="Common ESLint fix recipes for this project's config. Apply directly — do not search node_modules for types. The `rule` attribute maps to ESLint rule IDs for automatic matching by `lint-warnings.sh`.">

.cursor/scripts/pr-watch.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ while true; do
6464
NOW=$(date '+%H:%M:%S')
6565

6666
# Parse recommended interval from script output
67-
RECOMMENDED=$(echo "$OUTPUT" | sed -n 's/^# interval:\([0-9][0-9]*\)/\1/p' | head -n 1)
67+
RECOMMENDED=$(echo "$OUTPUT" | grep -oP '(?<=^# interval:)\d+' || echo "")
6868

6969
# Determine actual sleep interval
7070
if [[ -n "$INTERVAL" ]]; then

.cursor/scripts/push-env-key.sh

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
#!/usr/bin/env bash
2+
# push-env-key.sh — Update a single key in the server's env.json and push
3+
#
4+
# Usage: push-env-key.sh <KEY> <VALUE> [-m "commit message"]
5+
#
6+
# Examples:
7+
# push-env-key.sh EDGE_API_KEY abc123
8+
# push-env-key.sh EDGE_API_KEY abc123 -m "Rotate Edge API key"
9+
10+
set -euo pipefail
11+
12+
SERVER="jack"
13+
REMOTE_REPO="/home/jon/jenkins-files/master"
14+
15+
KEY=""
16+
VALUE=""
17+
COMMIT_MSG=""
18+
19+
while [[ $# -gt 0 ]]; do
20+
case "$1" in
21+
-m) COMMIT_MSG="$2"; shift 2 ;;
22+
*)
23+
if [[ -z "$KEY" ]]; then KEY="$1"
24+
elif [[ -z "$VALUE" ]]; then VALUE="$1"
25+
else echo "Unexpected argument: $1" >&2; exit 1
26+
fi
27+
shift ;;
28+
esac
29+
done
30+
31+
if [[ -z "$KEY" || -z "$VALUE" ]]; then
32+
echo "Usage: push-env-key.sh <KEY> <VALUE> [-m \"commit message\"]" >&2
33+
exit 1
34+
fi
35+
36+
if [[ -z "$COMMIT_MSG" ]]; then
37+
COMMIT_MSG="Update $KEY in env.json"
38+
fi
39+
40+
ssh "$SERVER" bash -s -- "$KEY" "$VALUE" "$COMMIT_MSG" "$REMOTE_REPO" <<'REMOTE'
41+
set -euo pipefail
42+
KEY="$1"
43+
VALUE="$2"
44+
MSG="$3"
45+
REPO="$4"
46+
47+
cd "$REPO"
48+
git pull --ff-only
49+
50+
CURRENT=$(jq -r --arg k "$KEY" '.[$k] // empty' env.json)
51+
if [[ "$CURRENT" == "$VALUE" ]]; then
52+
echo "No change: $KEY is already set to that value."
53+
exit 0
54+
fi
55+
56+
jq --arg k "$KEY" --arg v "$VALUE" '.[$k] = $v' env.json > env.json.tmp
57+
mv env.json.tmp env.json
58+
59+
git add env.json
60+
git commit -m "$MSG"
61+
git push
62+
echo "Done: $KEY updated and pushed."
63+
REMOTE

.cursor/skills/asana-task-update/SKILL.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
---
22
name: asana-task-update
33
description: Update Asana tasks via one reusable workflow (attach PRs, assign/unassign, set status, and update task fields). Use when any skill needs to modify Asana task state.
4-
compatibility: Requires jq. ASANA_TOKEN for Asana API updates. ASANA_GITHUB_SECRET for PR attach operations (obtain via https://github.integrations.asana.plus/auth?domainId=ghactions).
4+
compatibility: Requires jq. ASANA_TOKEN for Asana API updates. ASANA_GITHUB_SECRET for PR attach operations.
55
metadata:
66
author: j0ntz
77
---
@@ -11,7 +11,7 @@ metadata:
1111
<rules description="Non-negotiable constraints.">
1212
<rule id="use-companion-script">Use `~/.cursor/skills/asana-task-update/scripts/asana-task-update.sh` for all Asana task mutations. Do not call raw Asana APIs directly from skills that can delegate here.</rule>
1313
<rule id="task-required">Every operation requires `--task <task_gid>`.</rule>
14-
<rule id="attach-requires-secret">`--attach-pr` requires `ASANA_GITHUB_SECRET` (obtain via OAuth: https://github.integrations.asana.plus/auth?domainId=ghactions). Other operations require `ASANA_TOKEN`.</rule>
14+
<rule id="attach-requires-secret">`--attach-pr` requires `ASANA_GITHUB_SECRET`. Other operations require `ASANA_TOKEN`.</rule>
1515
<rule id="prompt-codes">If the script exits code 2 with `PROMPT_REVIEWER` or `PROMPT_IMPLEMENTOR`, ask the user and re-run with explicit `--reviewer` or `--implementor`.</rule>
1616
<rule id="script-timeouts">Asana updates can take time. Use `block_until_ms: 120000` for script calls.</rule>
1717
</rules>

.cursor/skills/asana-task-update/scripts/asana-task-update.sh

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,6 @@ fi
6969

7070
if $DO_ATTACH && [[ -z "${ASANA_GITHUB_SECRET:-}" ]]; then
7171
echo "Error: ASANA_GITHUB_SECRET not set (required for --attach-pr)" >&2
72-
echo "Obtain via OAuth: https://github.integrations.asana.plus/auth?domainId=ghactions" >&2
7372
exit 1
7473
fi
7574

@@ -91,7 +90,6 @@ status_to_gid() {
9190
"Review Needed") echo "$REVIEW_NEEDED_OPTION" ;;
9291
"Publish Needed") echo "$PUBLISH_NEEDED_OPTION" ;;
9392
"Verification Needed") echo "$VERIFICATION_NEEDED_OPTION" ;;
94-
# Passthrough: accepts raw GIDs from callers like asana-create-dep-task.sh
9593
*) echo "$1" ;;
9694
esac
9795
}

.cursor/skills/convention-sync/SKILL.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ metadata:
1010

1111
<rules>
1212
<rule id="local-is-canonical">`~/.cursor/` is the canonical source. Edits happen locally; the repo is the distribution copy. Default direction is `user-to-repo`. Use `--repo-to-user` only for onboarding or pulling changes authored by others. The script does not detect bidirectional conflicts — whichever direction you run overwrites the other side.</rule>
13-
<rule id="use-companion-script">Use `scripts/convention-sync.sh` for diffing and syncing. Do NOT manually diff or copy files.</rule>
13+
<rule id="use-companion-script">Use `~/.cursor/skills/convention-sync/scripts/convention-sync.sh` for diffing and syncing. Do NOT manually diff or copy files.</rule>
1414
<rule id="dry-run-first">Always run without `--stage` first to show the summary. Only stage/commit after user confirms.</rule>
1515
<rule id="no-script-bypass">If the script fails, report the error and STOP.</rule>
1616
<rule id="readme-is-source">`.cursor/README.md` is the source of truth for documentation. The script mirrors it to the PR description automatically.</rule>
@@ -23,7 +23,7 @@ Determine the repo directory — default to the current working directory if it
2323
Run **in parallel**:
2424
1. Sync script in dry-run mode:
2525
```bash
26-
scripts/convention-sync.sh <repo-dir>
26+
~/.cursor/skills/convention-sync/scripts/convention-sync.sh <repo-dir>
2727
```
2828
2. Check for open PR:
2929
```bash
@@ -57,7 +57,7 @@ If the user provided a commit message in their prompt, skip the confirmation and
5757
Run the script with `--commit`:
5858

5959
```bash
60-
scripts/convention-sync.sh <repo-dir> --commit -m "<message>"
60+
~/.cursor/skills/convention-sync/scripts/convention-sync.sh <repo-dir> --commit -m "<message>"
6161
```
6262

6363
Then push:

.cursor/skills/one-shot/SKILL.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
---
22
name: one-shot
33
description: End-to-end flow for a task: plan/context, implementation, PR creation, and Asana attach/assign in one command.
4-
compatibility: Requires git, gh, node, jq. ASANA_TOKEN for Asana integration. ASANA_GITHUB_SECRET for PR attachment (obtain via https://github.integrations.asana.plus/auth?domainId=ghactions).
4+
compatibility: Requires git, gh, node, jq. ASANA_TOKEN for Asana integration. ASANA_GITHUB_SECRET for PR attachment.
55
metadata:
66
author: j0ntz
77
---

.cursor/skills/pr-address/SKILL.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,14 @@ The `fetch` output contains:
4242
- **reviewBodies**: Latest review body per non-author reviewer (excludes `prAuthor` and bots)
4343
- **topLevel**: Top-level comments (excludes `prAuthor` and bots)
4444

45+
To inspect a specific inline thread, including an already-resolved one, use:
46+
47+
```bash
48+
~/.cursor/skills/pr-address/scripts/pr-address.sh fetch-thread \
49+
--owner <OWNER> --repo <REPO> --pr <NUMBER> \
50+
--thread-id "<PRRT_threadNodeId>"
51+
```
52+
4553
The `fetch-pr-body` call writes the current PR body to `/tmp/pr-body.md`. This file is available for editing throughout the session. If you need to update the PR body (e.g. to revise the description after addressing feedback), edit `/tmp/pr-body.md` via the Write tool and push it back:
4654

4755
```bash
@@ -89,6 +97,16 @@ git push
8997
After fixing, reply to every processed comment — addressed or rejected — then resolve it.
9098

9199
<sub-step name="Inline threads (reply → resolve)">
100+
If a later fix may affect an already-addressed inline thread, inspect the thread first:
101+
102+
```bash
103+
~/.cursor/skills/pr-address/scripts/pr-address.sh fetch-thread \
104+
--owner <OWNER> --repo <REPO> --pr <NUMBER> \
105+
--thread-id "<PRRT_threadNodeId>"
106+
```
107+
108+
Use the returned history to decide whether the existing reply still fully reflects the latest fix. If it does not, add one new factual follow-up reply. Multiple replies in the same thread are acceptable when they capture materially new fixes.
109+
92110
1. Reply to the first comment in the thread:
93111
```bash
94112
~/.cursor/skills/pr-address/scripts/pr-address.sh reply \
@@ -120,6 +138,8 @@ These have no native resolution mechanism. Post a top-level comment with a machi
120138
```
121139

122140
The script appends `<!-- addressed:review:ID -->` or `<!-- addressed:comment:ID -->` to the body. Subsequent `fetch` calls detect these markers and exclude already-addressed items.
141+
142+
**Skip bot-only no-op items**: If a review body or top-level comment is from a bot user (e.g., `cursor`, `chatgpt-codex-connector`) AND contains no inline threads with actionable suggestions — only a summary or status message — do NOT post a `mark-addressed` comment. Human reviewer items must always be addressed or rejected, even terse ones like "This needs work".
123143
</sub-step>
124144

125145
<sub-step name="Reply guidelines">
@@ -175,4 +195,5 @@ Propose modifications to `~/.cursor/rules/typescript-standards.mdc` to prevent s
175195
<case name="No unresolved feedback">Report "No unresolved comments on this PR" and STOP.</case>
176196
<case name="External human reviewer comments">Do NOT autosquash when `hasHumanReviewers` is true. Leave fixup commits for the external reviewer to verify, then squash on merge.</case>
177197
<case name="Comment already addressed in code">If the current code already handles the feedback (e.g., from a previous fixup), still reply explaining this and resolve/mark the comment. Do not leave it unresolved.</case>
198+
<case name="Already resolved thread needs follow-up">Fetch the thread history first. If the prior reply no longer reflects the latest fix, post one additional factual follow-up reply. Do not edit or delete prior replies in this workflow.</case>
178199
</edge-cases>

.cursor/skills/pr-address/scripts/pr-address.sh

Lines changed: 57 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
#
66
# Subcommands:
77
# fetch --owner <o> --repo <r> --pr <n> Fetch all unresolved feedback via GraphQL
8+
# fetch-thread --owner <o> --repo <r> --pr <n> --thread-id <id>
89
# reply --owner <o> --repo <r> --pr <n> --comment-id <id> --body <text>
910
# resolve-thread --thread-id <node_id> Mark inline thread as resolved (GraphQL)
1011
# mark-addressed --owner <o> --repo <r> --pr <n> --type <review|comment> --target-id <id> --body <text>
@@ -179,6 +180,61 @@ case "$CMD" in
179180
"
180181
;;
181182

183+
fetch-thread)
184+
require_gh
185+
if [[ -z "$OWNER" || -z "$REPO" || -z "$PR" || -z "$THREAD_ID" ]]; then
186+
echo "Error: --owner, --repo, --pr, --thread-id required" >&2; exit 1
187+
fi
188+
189+
gh api graphql \
190+
-f query='query($owner: String!, $repo: String!, $number: Int!) {
191+
repository(owner: $owner, name: $repo) {
192+
pullRequest(number: $number) {
193+
reviewThreads(first: 100) {
194+
nodes {
195+
id
196+
isResolved
197+
comments(first: 50) {
198+
nodes {
199+
databaseId
200+
createdAt
201+
author { login }
202+
path
203+
line
204+
body
205+
}
206+
}
207+
}
208+
}
209+
}
210+
}
211+
}' \
212+
-f owner="$OWNER" -f repo="$REPO" -F number="$PR" \
213+
| GH_THREAD_ID="$THREAD_ID" node -e "
214+
const fs = require('fs')
215+
const data = JSON.parse(fs.readFileSync('/dev/stdin', 'utf8'))
216+
const threads = data.data.repository.pullRequest.reviewThreads.nodes
217+
const thread = threads.find(item => item.id === process.env.GH_THREAD_ID)
218+
if (thread == null) {
219+
console.error('Thread not found: ' + process.env.GH_THREAD_ID)
220+
process.exit(1)
221+
}
222+
223+
console.log(JSON.stringify({
224+
threadId: thread.id,
225+
isResolved: thread.isResolved,
226+
path: thread.comments.nodes[0]?.path ?? null,
227+
line: thread.comments.nodes[0]?.line ?? null,
228+
comments: thread.comments.nodes.map(comment => ({
229+
id: comment.databaseId,
230+
user: comment.author?.login ?? null,
231+
body: comment.body,
232+
createdAt: comment.createdAt
233+
}))
234+
}, null, 2))
235+
"
236+
;;
237+
182238
reply)
183239
require_gh
184240
if [[ -z "$OWNER" || -z "$REPO" || -z "$PR" || -z "$COMMENT_ID" || -z "$BODY" ]]; then
@@ -269,7 +325,7 @@ case "$CMD" in
269325
;;
270326

271327
*)
272-
echo "Usage: pr-address.sh {fetch|reply|resolve-thread|mark-addressed|resolve-id|headline|fetch-pr-body|autosquash} [args]" >&2
328+
echo "Usage: pr-address.sh {fetch|fetch-thread|reply|resolve-thread|mark-addressed|resolve-id|headline|fetch-pr-body|autosquash} [args]" >&2
273329
exit 1
274330
;;
275331
esac

.cursor/skills/pr-create/SKILL.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
---
22
name: pr-create
33
description: Create a pull request from the current branch, with optional Asana attach/assign updates.
4-
compatibility: Requires git, gh, node, jq. ASANA_TOKEN for Asana updates. ASANA_GITHUB_SECRET for Asana PR attachment (obtain via https://github.integrations.asana.plus/auth?domainId=ghactions).
4+
compatibility: Requires git, gh, node, jq. ASANA_TOKEN for Asana updates. ASANA_GITHUB_SECRET for Asana PR attachment.
55
metadata:
66
author: j0ntz
77
---

0 commit comments

Comments
 (0)