mirror of
https://github.com/bitsocialnet/5chan.git
synced 2026-08-03 07:41:04 +02:00
chore(ai-workflow): tighten agent workflow and worktree tooling (#1107)
* chore(ai-workflow): tighten agent workflow and worktree tooling * fix(ai-workflow): address review feedback * fix(ai-workflow): make format hook portable
This commit is contained in:
+2
-25
@@ -1,27 +1,4 @@
|
||||
#!/bin/bash
|
||||
# afterFileEdit hook: Auto-format files after AI edits them
|
||||
# Receives JSON via stdin: {"file_path": "...", "edits": [...]}
|
||||
|
||||
# Read stdin (required for hooks)
|
||||
input=$(cat)
|
||||
|
||||
# Extract file_path using grep/sed (jq-free for portability)
|
||||
file_path=$(echo "$input" | grep -o '"file_path"[[:space:]]*:[[:space:]]*"[^"]*"' | sed 's/.*:.*"\([^"]*\)"/\1/')
|
||||
|
||||
# Exit if no file path found
|
||||
if [ -z "$file_path" ]; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Only format JS/TS files
|
||||
case "$file_path" in
|
||||
*.js|*.ts|*.tsx|*.mjs)
|
||||
# Match the other hooks by resolving relative paths from the repo root.
|
||||
cd "$(dirname "$0")/../.." || exit 0
|
||||
|
||||
# Run oxfmt on the file (silent on success)
|
||||
npx oxfmt "$file_path" 2>/dev/null || true
|
||||
;;
|
||||
esac
|
||||
|
||||
exit 0
|
||||
repo_root="$(cd "$(dirname "$0")/../.." && pwd)"
|
||||
exec "$repo_root/scripts/agent-hooks/format.sh" "$@"
|
||||
|
||||
@@ -1,110 +1,4 @@
|
||||
#!/bin/bash
|
||||
# stop hook: prune stale remote refs and remove integrated temporary local branches
|
||||
# This is informational - always exits 0
|
||||
|
||||
# Consume stdin (required for hooks)
|
||||
cat > /dev/null
|
||||
|
||||
# Change to project directory
|
||||
cd "$(dirname "$0")/../.." || exit 0
|
||||
|
||||
if ! git rev-parse --is-inside-work-tree >/dev/null 2>&1; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
default_branch="$(git symbolic-ref --quiet --short refs/remotes/origin/HEAD 2>/dev/null | sed 's#^origin/##')"
|
||||
if [ -z "$default_branch" ]; then
|
||||
default_branch="master"
|
||||
fi
|
||||
|
||||
current_branch="$(git branch --show-current 2>/dev/null || true)"
|
||||
|
||||
branch_looks_temporary() {
|
||||
case "$1" in
|
||||
pr/*|feature/*|fix/*|docs/*|chore/*) return 0 ;;
|
||||
*) return 1 ;;
|
||||
esac
|
||||
}
|
||||
|
||||
branch_is_integrated() {
|
||||
local branch="$1"
|
||||
local cherry_output
|
||||
|
||||
cherry_output="$(git cherry "$default_branch" "$branch" 2>/dev/null || true)"
|
||||
if echo "$cherry_output" | grep -q '^+'; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
branch_has_live_upstream() {
|
||||
local upstream="$1"
|
||||
[ -n "$upstream" ] && git show-ref --verify --quiet "refs/remotes/$upstream"
|
||||
}
|
||||
|
||||
merged_pr_number_for_branch() {
|
||||
local branch="$1"
|
||||
local pr_number=""
|
||||
|
||||
if ! command -v gh >/dev/null 2>&1; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
case "$branch" in
|
||||
pr/*)
|
||||
pr_number="${branch#pr/}"
|
||||
gh pr view "$pr_number" --repo bitsocialnet/5chan --json mergedAt --jq 'select(.mergedAt != null) | .mergedAt' >/dev/null 2>&1 || return 0
|
||||
echo "$pr_number"
|
||||
return 0
|
||||
;;
|
||||
esac
|
||||
|
||||
gh pr list --repo bitsocialnet/5chan --state merged --head "$branch" --json number --jq '.[0].number // empty' 2>/dev/null || true
|
||||
}
|
||||
|
||||
echo "Syncing git refs and temporary branches..."
|
||||
echo ""
|
||||
|
||||
echo "=== git config --local fetch.prune true ==="
|
||||
git config --local fetch.prune true 2>&1 || true
|
||||
echo ""
|
||||
|
||||
echo "=== git config --local remote.origin.prune true ==="
|
||||
git config --local remote.origin.prune true 2>&1 || true
|
||||
echo ""
|
||||
|
||||
echo "=== git fetch --prune origin ==="
|
||||
git fetch --prune origin 2>&1 || true
|
||||
echo ""
|
||||
|
||||
while IFS='|' read -r branch upstream; do
|
||||
local_pr_number=""
|
||||
|
||||
[ -z "$branch" ] && continue
|
||||
[ "$branch" = "$current_branch" ] && continue
|
||||
[ "$branch" = "$default_branch" ] && continue
|
||||
|
||||
branch_looks_temporary "$branch" || continue
|
||||
local_pr_number="$(merged_pr_number_for_branch "$branch")"
|
||||
|
||||
if branch_has_live_upstream "$upstream"; then
|
||||
continue
|
||||
fi
|
||||
|
||||
if ! branch_is_integrated "$branch" && [ -z "$local_pr_number" ]; then
|
||||
continue
|
||||
fi
|
||||
|
||||
if [ -n "$local_pr_number" ]; then
|
||||
echo "=== merged PR #$local_pr_number allows deleting $branch ==="
|
||||
echo ""
|
||||
fi
|
||||
|
||||
echo "=== git branch -D $branch ==="
|
||||
git branch -D "$branch" 2>&1 || true
|
||||
echo ""
|
||||
done < <(git for-each-ref --format='%(refname:short)|%(upstream:short)' refs/heads)
|
||||
|
||||
echo "Git ref sync complete."
|
||||
exit 0
|
||||
repo_root="$(cd "$(dirname "$0")/../.." && pwd)"
|
||||
exec "$repo_root/scripts/agent-hooks/sync-git-branches.sh" "$@"
|
||||
|
||||
+2
-61
@@ -1,63 +1,4 @@
|
||||
#!/bin/bash
|
||||
# stop hook: Run build, lint, type-check, and security audit when agent finishes
|
||||
# This is informational - always exits 0
|
||||
|
||||
# Consume stdin (required for hooks)
|
||||
cat > /dev/null
|
||||
|
||||
# Change to project directory
|
||||
cd "$(dirname "$0")/../.." || exit 0
|
||||
|
||||
cleanup_generated_dir() {
|
||||
local path="$1"
|
||||
|
||||
if ! git rev-parse --is-inside-work-tree >/dev/null 2>&1; then
|
||||
return
|
||||
fi
|
||||
|
||||
if git ls-files --error-unmatch "$path" >/dev/null 2>&1; then
|
||||
if git diff --quiet -- "$path"; then
|
||||
return
|
||||
fi
|
||||
|
||||
echo "=== git restore --worktree $path ==="
|
||||
git restore --worktree -- "$path" 2>&1 || true
|
||||
echo ""
|
||||
return
|
||||
fi
|
||||
|
||||
if [ -e "$path" ]; then
|
||||
echo "=== rm -rf $path ==="
|
||||
rm -rf "$path" 2>&1 || true
|
||||
echo ""
|
||||
fi
|
||||
}
|
||||
|
||||
echo "Running build, lint, type-check, and security audit..."
|
||||
echo ""
|
||||
|
||||
# Run build (catches compilation errors)
|
||||
echo "=== yarn build ==="
|
||||
yarn build 2>&1 || true
|
||||
echo ""
|
||||
|
||||
# Run lint
|
||||
echo "=== yarn lint ==="
|
||||
yarn lint 2>&1 || true
|
||||
echo ""
|
||||
|
||||
# Run type-check
|
||||
echo "=== yarn type-check ==="
|
||||
yarn type-check 2>&1 || true
|
||||
echo ""
|
||||
|
||||
# Run security audit
|
||||
echo "=== yarn audit ==="
|
||||
yarn audit 2>&1 || true
|
||||
echo ""
|
||||
|
||||
cleanup_generated_dir build
|
||||
cleanup_generated_dir dist
|
||||
|
||||
echo "Verification complete."
|
||||
exit 0
|
||||
repo_root="$(cd "$(dirname "$0")/../.." && pwd)"
|
||||
exec "$repo_root/scripts/agent-hooks/verify.sh" "$@"
|
||||
|
||||
@@ -1,25 +1,4 @@
|
||||
#!/bin/bash
|
||||
# afterFileEdit hook: Run yarn install when package.json is changed
|
||||
# Receives JSON via stdin: {"file_path": "...", "edits": [...]}
|
||||
|
||||
# Read stdin (required for hooks)
|
||||
input=$(cat)
|
||||
|
||||
# Extract file_path using grep/sed (jq-free for portability)
|
||||
file_path=$(echo "$input" | grep -o '"file_path"[[:space:]]*:[[:space:]]*"[^"]*"' | sed 's/.*:.*"\([^"]*\)"/\1/')
|
||||
|
||||
# Exit if no file path found
|
||||
if [ -z "$file_path" ]; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Only run yarn install if package.json was changed
|
||||
if [ "$file_path" = "package.json" ]; then
|
||||
# Change to project directory
|
||||
cd "$(dirname "$0")/../.." || exit 0
|
||||
|
||||
echo "package.json changed - running yarn install to update yarn.lock..."
|
||||
yarn install
|
||||
fi
|
||||
|
||||
exit 0
|
||||
repo_root="$(cd "$(dirname "$0")/../.." && pwd)"
|
||||
exec "$repo_root/scripts/agent-hooks/yarn-install.sh" "$@"
|
||||
|
||||
@@ -41,21 +41,21 @@ fi
|
||||
|
||||
### 3. Ensure branch workflow is reviewable
|
||||
|
||||
- If already on a short-lived task branch such as `feature/*`, `fix/*`, `docs/*`, or `chore/*`, stay on it.
|
||||
- If already on a short-lived task branch such as `codex/feature/*`, `codex/fix/*`, `codex/docs/*`, or `codex/chore/*`, stay on it.
|
||||
- If on `master`, create a task branch before staging or committing.
|
||||
- Do **not** commit the work directly on `master` when PR review bots are expected.
|
||||
|
||||
Suggested naming:
|
||||
|
||||
- `feature/short-slug`
|
||||
- `fix/short-slug`
|
||||
- `docs/short-slug`
|
||||
- `chore/short-slug`
|
||||
- `codex/feature/short-slug`
|
||||
- `codex/fix/short-slug`
|
||||
- `codex/docs/short-slug`
|
||||
- `codex/chore/short-slug`
|
||||
|
||||
Example:
|
||||
|
||||
```bash
|
||||
git switch -c fix/reply-editor-stuck
|
||||
git switch -c codex/fix/reply-editor-stuck
|
||||
```
|
||||
|
||||
### 4. Review diffs for relevance
|
||||
|
||||
+2
-25
@@ -1,27 +1,4 @@
|
||||
#!/bin/bash
|
||||
# afterFileEdit hook: Auto-format files after AI edits them
|
||||
# Receives JSON via stdin: {"file_path": "...", "edits": [...]}
|
||||
|
||||
# Read stdin (required for hooks)
|
||||
input=$(cat)
|
||||
|
||||
# Extract file_path using grep/sed (jq-free for portability)
|
||||
file_path=$(echo "$input" | grep -o '"file_path"[[:space:]]*:[[:space:]]*"[^"]*"' | sed 's/.*:.*"\([^"]*\)"/\1/')
|
||||
|
||||
# Exit if no file path found
|
||||
if [ -z "$file_path" ]; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Only format JS/TS files
|
||||
case "$file_path" in
|
||||
*.js|*.ts|*.tsx|*.mjs)
|
||||
# Match the other hooks by resolving relative paths from the repo root.
|
||||
cd "$(dirname "$0")/../.." || exit 0
|
||||
|
||||
# Run oxfmt on the file (silent on success)
|
||||
npx oxfmt "$file_path" 2>/dev/null || true
|
||||
;;
|
||||
esac
|
||||
|
||||
exit 0
|
||||
repo_root="$(cd "$(dirname "$0")/../.." && pwd)"
|
||||
exec "$repo_root/scripts/agent-hooks/format.sh" "$@"
|
||||
|
||||
@@ -1,110 +1,4 @@
|
||||
#!/bin/bash
|
||||
# stop hook: prune stale remote refs and remove integrated temporary local branches
|
||||
# This is informational - always exits 0
|
||||
|
||||
# Consume stdin (required for hooks)
|
||||
cat > /dev/null
|
||||
|
||||
# Change to project directory
|
||||
cd "$(dirname "$0")/../.." || exit 0
|
||||
|
||||
if ! git rev-parse --is-inside-work-tree >/dev/null 2>&1; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
default_branch="$(git symbolic-ref --quiet --short refs/remotes/origin/HEAD 2>/dev/null | sed 's#^origin/##')"
|
||||
if [ -z "$default_branch" ]; then
|
||||
default_branch="master"
|
||||
fi
|
||||
|
||||
current_branch="$(git branch --show-current 2>/dev/null || true)"
|
||||
|
||||
branch_looks_temporary() {
|
||||
case "$1" in
|
||||
pr/*|feature/*|fix/*|docs/*|chore/*) return 0 ;;
|
||||
*) return 1 ;;
|
||||
esac
|
||||
}
|
||||
|
||||
branch_is_integrated() {
|
||||
local branch="$1"
|
||||
local cherry_output
|
||||
|
||||
cherry_output="$(git cherry "$default_branch" "$branch" 2>/dev/null || true)"
|
||||
if echo "$cherry_output" | grep -q '^+'; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
branch_has_live_upstream() {
|
||||
local upstream="$1"
|
||||
[ -n "$upstream" ] && git show-ref --verify --quiet "refs/remotes/$upstream"
|
||||
}
|
||||
|
||||
merged_pr_number_for_branch() {
|
||||
local branch="$1"
|
||||
local pr_number=""
|
||||
|
||||
if ! command -v gh >/dev/null 2>&1; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
case "$branch" in
|
||||
pr/*)
|
||||
pr_number="${branch#pr/}"
|
||||
gh pr view "$pr_number" --repo bitsocialnet/5chan --json mergedAt --jq 'select(.mergedAt != null) | .mergedAt' >/dev/null 2>&1 || return 0
|
||||
echo "$pr_number"
|
||||
return 0
|
||||
;;
|
||||
esac
|
||||
|
||||
gh pr list --repo bitsocialnet/5chan --state merged --head "$branch" --json number --jq '.[0].number // empty' 2>/dev/null || true
|
||||
}
|
||||
|
||||
echo "Syncing git refs and temporary branches..."
|
||||
echo ""
|
||||
|
||||
echo "=== git config --local fetch.prune true ==="
|
||||
git config --local fetch.prune true 2>&1 || true
|
||||
echo ""
|
||||
|
||||
echo "=== git config --local remote.origin.prune true ==="
|
||||
git config --local remote.origin.prune true 2>&1 || true
|
||||
echo ""
|
||||
|
||||
echo "=== git fetch --prune origin ==="
|
||||
git fetch --prune origin 2>&1 || true
|
||||
echo ""
|
||||
|
||||
while IFS='|' read -r branch upstream; do
|
||||
local_pr_number=""
|
||||
|
||||
[ -z "$branch" ] && continue
|
||||
[ "$branch" = "$current_branch" ] && continue
|
||||
[ "$branch" = "$default_branch" ] && continue
|
||||
|
||||
branch_looks_temporary "$branch" || continue
|
||||
local_pr_number="$(merged_pr_number_for_branch "$branch")"
|
||||
|
||||
if branch_has_live_upstream "$upstream"; then
|
||||
continue
|
||||
fi
|
||||
|
||||
if ! branch_is_integrated "$branch" && [ -z "$local_pr_number" ]; then
|
||||
continue
|
||||
fi
|
||||
|
||||
if [ -n "$local_pr_number" ]; then
|
||||
echo "=== merged PR #$local_pr_number allows deleting $branch ==="
|
||||
echo ""
|
||||
fi
|
||||
|
||||
echo "=== git branch -D $branch ==="
|
||||
git branch -D "$branch" 2>&1 || true
|
||||
echo ""
|
||||
done < <(git for-each-ref --format='%(refname:short)|%(upstream:short)' refs/heads)
|
||||
|
||||
echo "Git ref sync complete."
|
||||
exit 0
|
||||
repo_root="$(cd "$(dirname "$0")/../.." && pwd)"
|
||||
exec "$repo_root/scripts/agent-hooks/sync-git-branches.sh" "$@"
|
||||
|
||||
+2
-61
@@ -1,63 +1,4 @@
|
||||
#!/bin/bash
|
||||
# stop hook: Run build, lint, type-check, and security audit when agent finishes
|
||||
# This is informational - always exits 0
|
||||
|
||||
# Consume stdin (required for hooks)
|
||||
cat > /dev/null
|
||||
|
||||
# Change to project directory
|
||||
cd "$(dirname "$0")/../.." || exit 0
|
||||
|
||||
cleanup_generated_dir() {
|
||||
local path="$1"
|
||||
|
||||
if ! git rev-parse --is-inside-work-tree >/dev/null 2>&1; then
|
||||
return
|
||||
fi
|
||||
|
||||
if git ls-files --error-unmatch "$path" >/dev/null 2>&1; then
|
||||
if git diff --quiet -- "$path"; then
|
||||
return
|
||||
fi
|
||||
|
||||
echo "=== git restore --worktree $path ==="
|
||||
git restore --worktree -- "$path" 2>&1 || true
|
||||
echo ""
|
||||
return
|
||||
fi
|
||||
|
||||
if [ -e "$path" ]; then
|
||||
echo "=== rm -rf $path ==="
|
||||
rm -rf "$path" 2>&1 || true
|
||||
echo ""
|
||||
fi
|
||||
}
|
||||
|
||||
echo "Running build, lint, type-check, and security audit..."
|
||||
echo ""
|
||||
|
||||
# Run build (catches compilation errors)
|
||||
echo "=== yarn build ==="
|
||||
yarn build 2>&1 || true
|
||||
echo ""
|
||||
|
||||
# Run lint
|
||||
echo "=== yarn lint ==="
|
||||
yarn lint 2>&1 || true
|
||||
echo ""
|
||||
|
||||
# Run type-check
|
||||
echo "=== yarn type-check ==="
|
||||
yarn type-check 2>&1 || true
|
||||
echo ""
|
||||
|
||||
# Run security audit
|
||||
echo "=== yarn audit ==="
|
||||
yarn audit 2>&1 || true
|
||||
echo ""
|
||||
|
||||
cleanup_generated_dir build
|
||||
cleanup_generated_dir dist
|
||||
|
||||
echo "Verification complete."
|
||||
exit 0
|
||||
repo_root="$(cd "$(dirname "$0")/../.." && pwd)"
|
||||
exec "$repo_root/scripts/agent-hooks/verify.sh" "$@"
|
||||
|
||||
@@ -1,25 +1,4 @@
|
||||
#!/bin/bash
|
||||
# afterFileEdit hook: Run yarn install when package.json is changed
|
||||
# Receives JSON via stdin: {"file_path": "...", "edits": [...]}
|
||||
|
||||
# Read stdin (required for hooks)
|
||||
input=$(cat)
|
||||
|
||||
# Extract file_path using grep/sed (jq-free for portability)
|
||||
file_path=$(echo "$input" | grep -o '"file_path"[[:space:]]*:[[:space:]]*"[^"]*"' | sed 's/.*:.*"\([^"]*\)"/\1/')
|
||||
|
||||
# Exit if no file path found
|
||||
if [ -z "$file_path" ]; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Only run yarn install if package.json was changed
|
||||
if [ "$file_path" = "package.json" ]; then
|
||||
# Change to project directory
|
||||
cd "$(dirname "$0")/../.." || exit 0
|
||||
|
||||
echo "package.json changed - running yarn install to update yarn.lock..."
|
||||
yarn install
|
||||
fi
|
||||
|
||||
exit 0
|
||||
repo_root="$(cd "$(dirname "$0")/../.." && pwd)"
|
||||
exec "$repo_root/scripts/agent-hooks/yarn-install.sh" "$@"
|
||||
|
||||
@@ -41,21 +41,21 @@ fi
|
||||
|
||||
### 3. Ensure branch workflow is reviewable
|
||||
|
||||
- If already on a short-lived task branch such as `feature/*`, `fix/*`, `docs/*`, or `chore/*`, stay on it.
|
||||
- If already on a short-lived task branch such as `codex/feature/*`, `codex/fix/*`, `codex/docs/*`, or `codex/chore/*`, stay on it.
|
||||
- If on `master`, create a task branch before staging or committing.
|
||||
- Do **not** commit the work directly on `master` when PR review bots are expected.
|
||||
|
||||
Suggested naming:
|
||||
|
||||
- `feature/short-slug`
|
||||
- `fix/short-slug`
|
||||
- `docs/short-slug`
|
||||
- `chore/short-slug`
|
||||
- `codex/feature/short-slug`
|
||||
- `codex/fix/short-slug`
|
||||
- `codex/docs/short-slug`
|
||||
- `codex/chore/short-slug`
|
||||
|
||||
Example:
|
||||
|
||||
```bash
|
||||
git switch -c fix/reply-editor-stuck
|
||||
git switch -c codex/fix/reply-editor-stuck
|
||||
```
|
||||
|
||||
### 4. Review diffs for relevance
|
||||
|
||||
@@ -33,7 +33,8 @@ Only record items that are repo-specific, likely to recur, and have a concrete m
|
||||
| Bug report in a specific file/line | Start with git history scan from `docs/agent-playbooks/bug-investigation.md` before editing |
|
||||
| `CHANGELOG.md` or package version changed | Run `yarn blotter:check`; if needed add a concise release one-liner |
|
||||
| UI/visual behavior changed | Verify in browser with `playwright-cli`; test desktop and mobile viewport |
|
||||
| New reviewable feature/fix started while on `master` | Create a short-lived `feature/*`, `fix/*`, `docs/*`, or `chore/*` branch from `master` before editing; use a separate worktree only for parallel tasks |
|
||||
| Long-running task spans multiple sessions, handoffs, or spawned agents | Use `docs/agent-playbooks/long-running-agent-workflow.md`, keep a machine-readable feature list plus a progress log, and run `./scripts/agent-init.sh --smoke` before starting a fresh feature slice |
|
||||
| New reviewable feature/fix started while on `master` | Create a short-lived `codex/feature/*`, `codex/fix/*`, `codex/docs/*`, or `codex/chore/*` branch from `master` before editing; use a separate worktree only for parallel tasks |
|
||||
| New unrelated task started while another task branch is already checked out or being worked on by another agent | Create a separate worktree from `master`, create a new short-lived task branch there, and keep each agent on its own worktree/branch/PR |
|
||||
| Open PR needs feedback triage or merge readiness check | Use the `review-and-merge-pr` skill to inspect bot/human feedback, fix valid findings, and merge only after verification |
|
||||
| Repo AI workflow files changed (`.codex/**`, `.cursor/**`) | Keep the Codex and Cursor copies aligned when they represent the same workflow; update `AGENTS.md` if the default agent policy changes |
|
||||
@@ -93,11 +94,12 @@ src/
|
||||
|
||||
- Keep `master` releasable. Do not treat `master` as a scratch branch.
|
||||
- If the user asks for a reviewable feature/fix and the current branch is `master`, create a short-lived task branch before making code changes unless the user explicitly asks to work directly on `master`.
|
||||
- Name short-lived branches by intent: `feature/*`, `fix/*`, `docs/*`, `chore/*`.
|
||||
- Name short-lived AI task branches by intent under the Codex prefix: `codex/feature/*`, `codex/fix/*`, `codex/docs/*`, `codex/chore/*`.
|
||||
- Open PRs from task branches into `master` so review bots can run against the actual change.
|
||||
- Prefer short-lived task branches over a long-lived `develop` branch unless the user explicitly asks for a staging branch workflow.
|
||||
- Use worktrees only when parallel tasks need isolated checkouts. One active task branch per worktree.
|
||||
- If a new task is unrelated to the currently checked out branch, do not stack it on that branch. Create a new worktree from `master` and create a separate short-lived task branch there.
|
||||
- Prefer `./scripts/create-task-worktree.sh <feature|fix|docs|chore> <slug>` when you need a new task worktree and do not have a stronger repo-specific reason to create it manually.
|
||||
- Treat branch and worktree as different things: the branch is the change set; the worktree is the checkout where that branch is worked on.
|
||||
- For parallel unrelated tasks, give each task its own branch from `master`, its own worktree, and its own PR into `master`.
|
||||
- After a reviewed branch is merged, prefer deleting it to keep branch drift and merge conflicts low.
|
||||
@@ -117,6 +119,7 @@ src/
|
||||
- After React UI logic changes, run: `yarn doctor`.
|
||||
- Treat React Doctor output as actionable guidance; prioritize `error` then `warning`.
|
||||
- For UI/visual changes, verify with `playwright-cli` on desktop and mobile viewport.
|
||||
- The shared hook verification path is strict by default. Only set `AGENT_VERIFY_MODE=advisory` when you intentionally need signal from a broken tree without blocking the session.
|
||||
- Use `yarn test:coverage` as an advisory check when expanding test coverage or auditing risky logic; do not invent a repo-wide coverage gate unless the user asks for one.
|
||||
- If verification fails, fix and re-run until passing.
|
||||
|
||||
@@ -135,6 +138,9 @@ src/
|
||||
- When changing shared agent behavior, update the relevant files in `.codex/skills/`, `.cursor/skills/`, `.codex/agents/`, `.cursor/agents/`, `.codex/hooks/`, `.cursor/hooks/`, and their `hooks.json` or config entry points as needed.
|
||||
- If `AGENTS.md` references a skill, agent, or hook, prefer a tracked file under `.codex/` or `.cursor/` rather than an untracked local-only instruction.
|
||||
- Review `.codex/config.toml` and `.cursor/hooks.json` before changing agent orchestration or hook behavior, because they are the entry points contributors will actually load.
|
||||
- Directory-specific auto-loaded rules live under `src/AGENTS.md` and `scripts/AGENTS.md`; read them before editing files in those trees.
|
||||
- For work expected to span multiple sessions, keep explicit task state in a `feature-list.json` plus `progress.md` pair using `docs/agent-playbooks/long-running-agent-workflow.md`.
|
||||
- If more than one human or toolchain needs the same task state, keep it in a tracked location such as `docs/agent-runs/<slug>/` instead of burying it in a tool-specific hidden directory.
|
||||
|
||||
### Project Maintenance Rules
|
||||
|
||||
@@ -184,6 +190,8 @@ yarn electron
|
||||
yarn doctor
|
||||
yarn doctor:score
|
||||
yarn doctor:verbose
|
||||
./scripts/create-task-worktree.sh chore ai-workflow-improvement
|
||||
./scripts/agent-init.sh --smoke
|
||||
```
|
||||
|
||||
## Playbooks (Load On Demand)
|
||||
@@ -191,6 +199,7 @@ yarn doctor:verbose
|
||||
Use these only when relevant to the active task:
|
||||
|
||||
- Hooks setup and scripts: `docs/agent-playbooks/hooks-setup.md`
|
||||
- Long-running agent workflow: `docs/agent-playbooks/long-running-agent-workflow.md`
|
||||
- Translations workflow: `docs/agent-playbooks/translations.md`
|
||||
- Commit/issue output format: `docs/agent-playbooks/commit-issue-format.md`
|
||||
- Skills/tools setup and MCP rationale: `docs/agent-playbooks/skills-and-tools.md`
|
||||
|
||||
@@ -6,9 +6,10 @@ If your AI coding assistant supports lifecycle hooks, configure these for this r
|
||||
|
||||
| Hook | Command | Purpose |
|
||||
|---|---|---|
|
||||
| `afterFileEdit` | `npx oxfmt <file>` | Auto-format files after AI edits |
|
||||
| `afterFileEdit` | `.cursor/hooks/yarn-install.sh` | Run `yarn install` when `package.json` changes |
|
||||
| `stop` | `yarn build && yarn lint && yarn type-check && (yarn audit || true)` | Build, lint, type-check, and security audit at end |
|
||||
| `afterFileEdit` | `scripts/agent-hooks/format.sh` | Auto-format files after AI edits |
|
||||
| `afterFileEdit` | `scripts/agent-hooks/yarn-install.sh` | Run `yarn install` when `package.json` changes |
|
||||
| `stop` | `scripts/agent-hooks/sync-git-branches.sh` | Prune stale refs and delete integrated temporary task branches |
|
||||
| `stop` | `scripts/agent-hooks/verify.sh` | Hard-gate build, lint, and type-check; keep `yarn audit` informational |
|
||||
|
||||
## Why
|
||||
|
||||
@@ -16,6 +17,8 @@ If your AI coding assistant supports lifecycle hooks, configure these for this r
|
||||
- Lockfile stays in sync
|
||||
- Build/lint/type issues caught early
|
||||
- Security visibility via `yarn audit`
|
||||
- One shared hook implementation for both Codex and Cursor
|
||||
- Temporary task branches stay aligned with the repo's worktree workflow
|
||||
|
||||
## Example Hook Scripts
|
||||
|
||||
@@ -42,13 +45,16 @@ exit 0
|
||||
# Run build, lint, type-check, and security audit when agent finishes
|
||||
|
||||
cat > /dev/null # consume stdin
|
||||
echo "=== yarn build ===" && yarn build
|
||||
echo "=== yarn lint ===" && yarn lint
|
||||
echo "=== yarn type-check ===" && yarn type-check
|
||||
status=0
|
||||
yarn build || status=1
|
||||
yarn lint || status=1
|
||||
yarn type-check || status=1
|
||||
echo "=== yarn audit ===" && (yarn audit || true) # informational
|
||||
exit 0
|
||||
exit $status
|
||||
```
|
||||
|
||||
By default, `scripts/agent-hooks/verify.sh` exits non-zero when `yarn build`, `yarn lint`, or `yarn type-check` fails. Set `AGENT_VERIFY_MODE=advisory` only when you intentionally need signal from a broken tree without blocking the hook.
|
||||
|
||||
### Yarn Install Hook
|
||||
|
||||
```bash
|
||||
@@ -73,3 +79,5 @@ exit 0
|
||||
```
|
||||
|
||||
Configure hook wiring according to your agent tool docs (`hooks.json`, equivalent, etc.).
|
||||
|
||||
In this repo, `.codex/hooks/*.sh` and `.cursor/hooks/*.sh` should stay as thin wrappers that delegate to the shared implementations under `scripts/agent-hooks/`.
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
# Long-Running Agent Workflow
|
||||
|
||||
Use this playbook when a task is likely to span multiple sessions, handoffs, or spawned agents.
|
||||
|
||||
## Goals
|
||||
|
||||
- Give each fresh session a fast way to regain context
|
||||
- Keep work incremental instead of one-shotting a large change
|
||||
- Catch a broken local baseline before adding more code
|
||||
- Leave durable artifacts that the next session can trust
|
||||
|
||||
## Where to Keep State
|
||||
|
||||
- Use `docs/agent-runs/<slug>/` when humans, review bots, or multiple toolchains need the same task state.
|
||||
- Use a tool-local directory such as `.codex/runs/<slug>/` only when the task state is intentionally local to one workstation or one toolchain.
|
||||
- Do not hide multi-session shared state in a private scratch file if another contributor or agent will need it later.
|
||||
|
||||
## Required Files
|
||||
|
||||
Create these files at the start of the long-running task:
|
||||
|
||||
- `feature-list.json`
|
||||
- `progress.md`
|
||||
|
||||
Use the templates in `docs/agent-playbooks/templates/feature-list.template.json` and `docs/agent-playbooks/templates/progress.template.md`.
|
||||
|
||||
Prefer JSON for the feature list so agents can update a small number of fields without rewriting the whole document.
|
||||
|
||||
## Session Start Checklist
|
||||
|
||||
1. Run `pwd`.
|
||||
2. Read `progress.md`.
|
||||
3. Read `feature-list.json`.
|
||||
4. Run `git log --oneline -20`.
|
||||
5. Run `./scripts/agent-init.sh --smoke`.
|
||||
6. Choose exactly one highest-priority item that is still `pending`, `in_progress`, or `blocked`.
|
||||
|
||||
If the smoke step fails, fix the broken baseline before implementing a new feature slice.
|
||||
|
||||
## Session Rules
|
||||
|
||||
- Work on one feature or task slice at a time.
|
||||
- Keep the feature list machine-readable and stable. Update status, notes, files, and verification fields instead of rewriting unrelated items.
|
||||
- Only mark an item verified after running the command or user flow listed in that item.
|
||||
- Use spawned agents for bounded slices, not for overall task-state ownership.
|
||||
- When a child agent owns one item, give it the exact item id, acceptance criteria, and files it may touch.
|
||||
|
||||
## Session End Checklist
|
||||
|
||||
1. Append a short progress entry to `progress.md`.
|
||||
2. Update the touched item in `feature-list.json`.
|
||||
3. Record the exact commands run for verification.
|
||||
4. Capture blockers, follow-ups, and the next best item to resume.
|
||||
|
||||
## Recommended Progress Entry Shape
|
||||
|
||||
Use a short structure like:
|
||||
|
||||
```markdown
|
||||
## 2026-03-17 14:30
|
||||
|
||||
- Item: F003
|
||||
- Summary: Updated the browser-check flow to use the shared init/bootstrap path.
|
||||
- Files: `.cursor/agents/browser-check.md`, `.codex/agents/browser-check.toml`
|
||||
- Verification: `yarn build`, `yarn lint`, `yarn type-check`
|
||||
- Next: Run the smoke flow and update the task-board status.
|
||||
```
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"task": "replace-with-task-slug",
|
||||
"last_updated": "YYYY-MM-DD",
|
||||
"items": [
|
||||
{
|
||||
"id": "F001",
|
||||
"priority": 1,
|
||||
"status": "pending",
|
||||
"description": "Describe one end-to-end feature or one reviewable task slice.",
|
||||
"verification": [
|
||||
"List the command or user-visible check that proves this item works."
|
||||
],
|
||||
"files": [],
|
||||
"notes": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
# Progress Log
|
||||
|
||||
Append one entry per session.
|
||||
|
||||
## YYYY-MM-DD HH:MM
|
||||
|
||||
- Item: F001
|
||||
- Summary: Replace this with the session summary.
|
||||
- Files: `path/to/file`
|
||||
- Verification: `yarn build`, `yarn lint`, `yarn type-check`
|
||||
- Blockers: none
|
||||
- Next: Replace this with the next best follow-up.
|
||||
@@ -0,0 +1,10 @@
|
||||
# scripts/AGENTS.md
|
||||
|
||||
These rules apply to `scripts/**`. Follow the repo-root `AGENTS.md` first, then use this file for automation and workflow helpers.
|
||||
|
||||
- Keep scripts non-interactive and idempotent. Print the command, URL, branch, or path being acted on so failures are diagnosable.
|
||||
- Use repo-relative paths and environment variables instead of user-specific absolute paths.
|
||||
- For dev-server helpers, default to `http://5chan.localhost:1355` and respect the existing `PORTLESS=0` fallback instead of hard-coding alternate ports.
|
||||
- Keep shell helpers thin. When logic becomes stateful or cross-platform, prefer a Node script.
|
||||
- Git and worktree helpers must validate input and default to safe operations.
|
||||
- If a helper deletes local branches automatically, document the exact eligibility checks and keep the behavior conservative.
|
||||
Executable
+37
@@ -0,0 +1,37 @@
|
||||
#!/bin/bash
|
||||
|
||||
# afterFileEdit hook: Auto-format files after AI edits them
|
||||
# Receives JSON via stdin: {"file_path": "...", "edits": [...]}
|
||||
|
||||
input=$(cat)
|
||||
if ! command -v jq >/dev/null 2>&1; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
file_path=$(printf '%s' "$input" | jq -r '.file_path // empty' 2>/dev/null)
|
||||
|
||||
if [ -z "$file_path" ]; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
repo_root="$(git rev-parse --show-toplevel 2>/dev/null || pwd)"
|
||||
cd "$repo_root" || exit 0
|
||||
|
||||
case "$file_path" in
|
||||
*.js|*.ts|*.tsx|*.mjs)
|
||||
dir_part="${file_path%/*}"
|
||||
base_name="${file_path##*/}"
|
||||
if [ "$dir_part" = "$file_path" ]; then
|
||||
dir_part="."
|
||||
fi
|
||||
|
||||
resolved_dir="$(cd -P -- "$repo_root/$dir_part" 2>/dev/null && pwd -P)" || exit 0
|
||||
resolved_path="$resolved_dir/$base_name"
|
||||
case "$resolved_path" in
|
||||
"$repo_root"/*) npx oxfmt "$resolved_path" 2>/dev/null || true ;;
|
||||
*) exit 0 ;;
|
||||
esac
|
||||
;;
|
||||
esac
|
||||
|
||||
exit 0
|
||||
Executable
+115
@@ -0,0 +1,115 @@
|
||||
#!/bin/bash
|
||||
|
||||
# stop hook: prune stale remote refs and remove integrated temporary local branches
|
||||
# This is informational - always exits 0
|
||||
|
||||
cat > /dev/null
|
||||
|
||||
cd "$(git rev-parse --show-toplevel 2>/dev/null || pwd)" || exit 0
|
||||
|
||||
if ! git rev-parse --is-inside-work-tree >/dev/null 2>&1; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
default_branch="$(git symbolic-ref --quiet --short refs/remotes/origin/HEAD 2>/dev/null | sed 's#^origin/##')"
|
||||
if [ -z "$default_branch" ]; then
|
||||
default_branch="master"
|
||||
fi
|
||||
|
||||
current_branch="$(git branch --show-current 2>/dev/null || true)"
|
||||
|
||||
branch_looks_temporary() {
|
||||
case "$1" in
|
||||
pr/*|feature/*|fix/*|docs/*|chore/*|codex/pr/*|codex/feature/*|codex/fix/*|codex/docs/*|codex/chore/*) return 0 ;;
|
||||
*) return 1 ;;
|
||||
esac
|
||||
}
|
||||
|
||||
branch_is_integrated() {
|
||||
local branch="$1"
|
||||
local cherry_output
|
||||
|
||||
cherry_output="$(git cherry "$default_branch" "$branch" 2>/dev/null || true)"
|
||||
if echo "$cherry_output" | grep -q '^+'; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
branch_has_live_upstream() {
|
||||
local upstream="$1"
|
||||
[ -n "$upstream" ] && git show-ref --verify --quiet "refs/remotes/$upstream"
|
||||
}
|
||||
|
||||
merged_pr_number_for_branch() {
|
||||
local branch="$1"
|
||||
local pr_number=""
|
||||
|
||||
if ! command -v gh >/dev/null 2>&1; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
case "$branch" in
|
||||
pr/*)
|
||||
pr_number="${branch#pr/}"
|
||||
;;
|
||||
codex/pr/*)
|
||||
pr_number="${branch#codex/pr/}"
|
||||
;;
|
||||
esac
|
||||
|
||||
if [ -n "$pr_number" ]; then
|
||||
gh pr view "$pr_number" --repo bitsocialnet/5chan --json mergedAt --jq 'select(.mergedAt != null) | .mergedAt' >/dev/null 2>&1 || return 0
|
||||
echo "$pr_number"
|
||||
return 0
|
||||
fi
|
||||
|
||||
gh pr list --repo bitsocialnet/5chan --state merged --head "$branch" --json number --jq '.[0].number // empty' 2>/dev/null || true
|
||||
}
|
||||
|
||||
echo "Syncing git refs and temporary branches..."
|
||||
echo ""
|
||||
|
||||
echo "=== git config --local fetch.prune true ==="
|
||||
git config --local fetch.prune true 2>&1 || true
|
||||
echo ""
|
||||
|
||||
echo "=== git config --local remote.origin.prune true ==="
|
||||
git config --local remote.origin.prune true 2>&1 || true
|
||||
echo ""
|
||||
|
||||
echo "=== git fetch --prune origin ==="
|
||||
git fetch --prune origin 2>&1 || true
|
||||
echo ""
|
||||
|
||||
while IFS='|' read -r branch upstream; do
|
||||
local_pr_number=""
|
||||
|
||||
[ -z "$branch" ] && continue
|
||||
[ "$branch" = "$current_branch" ] && continue
|
||||
[ "$branch" = "$default_branch" ] && continue
|
||||
|
||||
branch_looks_temporary "$branch" || continue
|
||||
local_pr_number="$(merged_pr_number_for_branch "$branch")"
|
||||
|
||||
if branch_has_live_upstream "$upstream"; then
|
||||
continue
|
||||
fi
|
||||
|
||||
if ! branch_is_integrated "$branch" && [ -z "$local_pr_number" ]; then
|
||||
continue
|
||||
fi
|
||||
|
||||
if [ -n "$local_pr_number" ]; then
|
||||
echo "=== merged PR #$local_pr_number allows deleting $branch ==="
|
||||
echo ""
|
||||
fi
|
||||
|
||||
echo "=== git branch -d $branch ==="
|
||||
git branch -d "$branch" 2>&1 || true
|
||||
echo ""
|
||||
done < <(git for-each-ref --format='%(refname:short)|%(upstream:short)' refs/heads)
|
||||
|
||||
echo "Git ref sync complete."
|
||||
exit 0
|
||||
Executable
+84
@@ -0,0 +1,84 @@
|
||||
#!/bin/bash
|
||||
|
||||
# stop hook: run required repo verification commands for agent-driven changes
|
||||
|
||||
set -u
|
||||
|
||||
mode="${AGENT_VERIFY_MODE:-strict}"
|
||||
|
||||
if [ "${1:-}" = "--advisory" ]; then
|
||||
mode="advisory"
|
||||
shift
|
||||
fi
|
||||
|
||||
cat > /dev/null
|
||||
|
||||
cd "$(git rev-parse --show-toplevel 2>/dev/null || pwd)" || exit 0
|
||||
|
||||
cleanup_generated_dir() {
|
||||
local path="$1"
|
||||
|
||||
if ! git rev-parse --is-inside-work-tree >/dev/null 2>&1; then
|
||||
return
|
||||
fi
|
||||
|
||||
if git ls-files --error-unmatch "$path" >/dev/null 2>&1; then
|
||||
if git diff --quiet -- "$path"; then
|
||||
return
|
||||
fi
|
||||
|
||||
echo "=== git restore --worktree $path ==="
|
||||
git restore --worktree -- "$path" 2>&1 || true
|
||||
echo ""
|
||||
return
|
||||
fi
|
||||
|
||||
if [ -e "$path" ]; then
|
||||
echo "=== rm -rf $path ==="
|
||||
rm -rf "$path" 2>&1 || true
|
||||
echo ""
|
||||
fi
|
||||
}
|
||||
|
||||
run_required_check() {
|
||||
local label="$1"
|
||||
shift
|
||||
|
||||
echo "=== $label ==="
|
||||
if "$@" 2>&1; then
|
||||
echo ""
|
||||
return 0
|
||||
fi
|
||||
|
||||
echo ""
|
||||
return 1
|
||||
}
|
||||
|
||||
echo "Running build, lint, type-check, and security audit..."
|
||||
echo ""
|
||||
|
||||
failures=0
|
||||
|
||||
run_required_check "yarn build" yarn build || failures=1
|
||||
run_required_check "yarn lint" yarn lint || failures=1
|
||||
run_required_check "yarn type-check" yarn type-check || failures=1
|
||||
|
||||
echo "=== yarn audit ==="
|
||||
yarn audit 2>&1 || true
|
||||
echo ""
|
||||
|
||||
cleanup_generated_dir build
|
||||
cleanup_generated_dir dist
|
||||
|
||||
if [ "$failures" -ne 0 ]; then
|
||||
if [ "$mode" = "advisory" ]; then
|
||||
echo "Verification failed, but AGENT_VERIFY_MODE=advisory so the hook is exiting 0."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "Verification failed."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Verification complete."
|
||||
exit 0
|
||||
Executable
+20
@@ -0,0 +1,20 @@
|
||||
#!/bin/bash
|
||||
|
||||
# afterFileEdit hook: Run yarn install when package.json is changed
|
||||
# Receives JSON via stdin: {"file_path": "...", "edits": [...]}
|
||||
|
||||
input=$(cat)
|
||||
file_path=$(echo "$input" | grep -o '"file_path"[[:space:]]*:[[:space:]]*"[^"]*"' | sed 's/.*:.*"\([^"]*\)"/\1/')
|
||||
|
||||
if [ -z "$file_path" ]; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [ "$file_path" = "package.json" ]; then
|
||||
repo_root="$(git rev-parse --show-toplevel 2>/dev/null || pwd)"
|
||||
cd "$repo_root" || exit 0
|
||||
echo "package.json changed - running yarn install to update yarn.lock..."
|
||||
yarn install
|
||||
fi
|
||||
|
||||
exit 0
|
||||
Executable
+70
@@ -0,0 +1,70 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
run_smoke=0
|
||||
wait_timeout="${AGENT_INIT_TIMEOUT_SECONDS:-60}"
|
||||
app_url="${AGENT_APP_URL:-http://5chan.localhost:1355}"
|
||||
|
||||
while [ "$#" -gt 0 ]; do
|
||||
case "$1" in
|
||||
--smoke)
|
||||
run_smoke=1
|
||||
;;
|
||||
*)
|
||||
echo "Unknown argument: $1" >&2
|
||||
echo "Usage: ./scripts/agent-init.sh [--smoke]" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
shift
|
||||
done
|
||||
|
||||
repo_root="$(git rev-parse --show-toplevel)"
|
||||
log_dir="$repo_root/.playwright-cli"
|
||||
log_path="${AGENT_START_LOG:-$log_dir/agent-start.log}"
|
||||
|
||||
mkdir -p "$log_dir"
|
||||
cd "$repo_root"
|
||||
|
||||
is_server_up() {
|
||||
curl -fsS "$app_url" >/dev/null 2>&1
|
||||
}
|
||||
|
||||
wait_for_server() {
|
||||
local started_at
|
||||
|
||||
started_at="$(date +%s)"
|
||||
while [ $(( $(date +%s) - started_at )) -lt "$wait_timeout" ]; do
|
||||
if is_server_up; then
|
||||
return 0
|
||||
fi
|
||||
sleep 1
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
echo "Repo root: $repo_root"
|
||||
echo "App URL: $app_url"
|
||||
|
||||
if is_server_up; then
|
||||
echo "Dev server is already reachable."
|
||||
else
|
||||
echo "Dev server is not reachable. Starting yarn start..."
|
||||
nohup yarn start >"$log_path" 2>&1 &
|
||||
echo "Startup log: $log_path"
|
||||
|
||||
if ! wait_for_server; then
|
||||
echo "Timed out waiting for $app_url" >&2
|
||||
echo "Last log lines:" >&2
|
||||
tail -n 40 "$log_path" >&2 || true
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "Dev server is ready."
|
||||
|
||||
if [ "$run_smoke" -eq 1 ]; then
|
||||
echo "Running smoke flow against the live dev server..."
|
||||
SMOKE_BASE_URL="${app_url%/}/#/" node scripts/smoke-web-app.js
|
||||
fi
|
||||
Executable
+66
@@ -0,0 +1,66 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
usage() {
|
||||
echo "Usage: ./scripts/create-task-worktree.sh <feature|fix|docs|chore> <slug> [base-branch] [worktree-path]"
|
||||
}
|
||||
|
||||
if [ "$#" -lt 2 ]; then
|
||||
usage >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
task_type="$1"
|
||||
slug_input="$2"
|
||||
base_branch="${3:-master}"
|
||||
|
||||
case "$task_type" in
|
||||
feature|fix|docs|chore) ;;
|
||||
*)
|
||||
echo "Unsupported task type: $task_type" >&2
|
||||
usage >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
slug="$(printf '%s' "$slug_input" | tr '[:upper:]' '[:lower:]' | sed -E 's/[^a-z0-9._-]+/-/g; s/^-+//; s/-+$//')"
|
||||
|
||||
if [ -z "$slug" ]; then
|
||||
echo "Slug must contain at least one letter or number." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
repo_root="$(git rev-parse --show-toplevel)"
|
||||
repo_name="$(basename "$repo_root")"
|
||||
default_worktree_path="$(dirname "$repo_root")/${repo_name}-${slug}"
|
||||
worktree_path="${4:-$default_worktree_path}"
|
||||
branch_name="codex/${task_type}/${slug}"
|
||||
|
||||
if git show-ref --verify --quiet "refs/heads/$branch_name"; then
|
||||
echo "Branch already exists: $branch_name" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -e "$worktree_path" ]; then
|
||||
echo "Worktree path already exists: $worktree_path" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if git show-ref --verify --quiet "refs/heads/$base_branch"; then
|
||||
base_ref="$base_branch"
|
||||
elif git show-ref --verify --quiet "refs/remotes/origin/$base_branch"; then
|
||||
base_ref="origin/$base_branch"
|
||||
else
|
||||
echo "Base branch not found locally or on origin: $base_branch" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Creating branch $branch_name from $base_ref"
|
||||
echo "Creating worktree at $worktree_path"
|
||||
git worktree add "$worktree_path" -b "$branch_name" "$base_ref"
|
||||
|
||||
echo ""
|
||||
echo "Worktree ready."
|
||||
echo "Branch: $branch_name"
|
||||
echo "Path: $worktree_path"
|
||||
@@ -0,0 +1,9 @@
|
||||
# src/AGENTS.md
|
||||
|
||||
These rules apply to `src/**`. Follow the repo-root `AGENTS.md` first, then use this file for code inside the application source tree.
|
||||
|
||||
- Keep route composition in `src/views/`, reusable UI in `src/components/`, shared logic in `src/hooks/`, and shared app state in `src/stores/`.
|
||||
- Before adding new state, decide whether it belongs in render, a reusable hook, or a Zustand store. Do not duplicate the same state logic across views.
|
||||
- Use `@bitsocialnet/bitsocial-react-hooks` for data access. Do not add data-fetching `useEffect` calls or effects that only synchronize derived state.
|
||||
- When changing React UI logic, run `yarn doctor` in addition to build, lint, and type-check. When changing layout or interaction, verify desktop and mobile behavior with `playwright-cli`.
|
||||
- Prefer extending nearby tests under `src/**/__tests__/` when touching already-covered behavior.
|
||||
Reference in New Issue
Block a user