chief-clancy 0.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.
Files changed (44) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +426 -0
  3. package/bin/install.js +169 -0
  4. package/package.json +42 -0
  5. package/registry/boards.json +47 -0
  6. package/src/agents/arch-agent.md +72 -0
  7. package/src/agents/concerns-agent.md +89 -0
  8. package/src/agents/design-agent.md +130 -0
  9. package/src/agents/quality-agent.md +161 -0
  10. package/src/agents/tech-agent.md +92 -0
  11. package/src/commands/doctor.md +7 -0
  12. package/src/commands/help.md +50 -0
  13. package/src/commands/init.md +7 -0
  14. package/src/commands/logs.md +7 -0
  15. package/src/commands/map-codebase.md +16 -0
  16. package/src/commands/once.md +13 -0
  17. package/src/commands/review.md +9 -0
  18. package/src/commands/run.md +11 -0
  19. package/src/commands/settings.md +7 -0
  20. package/src/commands/status.md +9 -0
  21. package/src/commands/uninstall.md +5 -0
  22. package/src/commands/update-docs.md +9 -0
  23. package/src/commands/update.md +9 -0
  24. package/src/templates/.env.example.github +43 -0
  25. package/src/templates/.env.example.jira +53 -0
  26. package/src/templates/.env.example.linear +43 -0
  27. package/src/templates/CLAUDE.md +69 -0
  28. package/src/templates/scripts/clancy-afk.sh +111 -0
  29. package/src/templates/scripts/clancy-once-github.sh +225 -0
  30. package/src/templates/scripts/clancy-once-linear.sh +252 -0
  31. package/src/templates/scripts/clancy-once.sh +259 -0
  32. package/src/workflows/doctor.md +115 -0
  33. package/src/workflows/init.md +423 -0
  34. package/src/workflows/logs.md +82 -0
  35. package/src/workflows/map-codebase.md +124 -0
  36. package/src/workflows/once.md +80 -0
  37. package/src/workflows/review.md +165 -0
  38. package/src/workflows/run.md +119 -0
  39. package/src/workflows/scaffold.md +289 -0
  40. package/src/workflows/settings.md +344 -0
  41. package/src/workflows/status.md +120 -0
  42. package/src/workflows/uninstall.md +75 -0
  43. package/src/workflows/update-docs.md +89 -0
  44. package/src/workflows/update.md +67 -0
@@ -0,0 +1,13 @@
1
+ # /clancy:once
2
+
3
+ Pick up exactly one ticket from your Kanban board, implement it, commit, squash-merge, and stop.
4
+
5
+ Good for:
6
+ - Your first run — watch Clancy work before going AFK
7
+ - Testing after changing .clancy/docs/ or CLAUDE.md
8
+ - Debugging a specific ticket
9
+ - When you only have time for one ticket
10
+
11
+ @.claude/clancy/workflows/once.md
12
+
13
+ Run one ticket as documented in the workflow above.
@@ -0,0 +1,9 @@
1
+ # /clancy:review
2
+
3
+ Fetch the next ticket from the board and score it — returns a confidence score (0–100%) and actionable recommendations before you run.
4
+
5
+ Does not implement anything. Read-only from Clancy's perspective, though it invokes Claude for analysis.
6
+
7
+ @.claude/clancy/workflows/review.md
8
+
9
+ Score the ticket as documented in the workflow above, including the full 7-criterion rubric and confidence bands.
@@ -0,0 +1,11 @@
1
+ # /clancy:run
2
+
3
+ Run Clancy in loop mode — processes tickets from your Kanban board until the queue is empty or MAX_ITERATIONS is reached.
4
+
5
+ Usage:
6
+ /clancy:run — uses MAX_ITERATIONS from .clancy/.env (default 5)
7
+ /clancy:run 20 — overrides MAX_ITERATIONS to 20 for this session only
8
+
9
+ @.claude/clancy/workflows/run.md
10
+
11
+ Run the loop as documented in the workflow above. The numeric argument (if provided) is session-only and never written to .clancy/.env.
@@ -0,0 +1,7 @@
1
+ # /clancy:settings
2
+
3
+ View and change Clancy configuration without re-running init.
4
+
5
+ @.claude/clancy/workflows/settings.md
6
+
7
+ Run the settings workflow as documented above.
@@ -0,0 +1,9 @@
1
+ # /clancy:status
2
+
3
+ Show the next tickets Clancy would pick up — read-only board check, no side effects.
4
+
5
+ Fetches up to 3 tickets from the queue using the same query as /clancy:once, so what you see here is exactly what Clancy would run next.
6
+
7
+ @.claude/clancy/workflows/status.md
8
+
9
+ Show status as documented in the workflow above. Strictly read-only — no git operations, no file writes, no ticket claiming.
@@ -0,0 +1,5 @@
1
+ /clancy:uninstall
2
+
3
+ Remove Clancy's slash commands from this project or your global Claude Code installation.
4
+
5
+ @.claude/clancy/workflows/uninstall.md
@@ -0,0 +1,9 @@
1
+ # /clancy:update-docs
2
+
3
+ Incrementally refresh .clancy/docs/ — re-runs only the agents covering areas that have changed since the last scan.
4
+
5
+ Faster than a full map-codebase when you've made targeted changes (new dependency, design system update, new test strategy, etc.).
6
+
7
+ @.claude/clancy/workflows/update-docs.md
8
+
9
+ Run the incremental update as documented in the workflow above.
@@ -0,0 +1,9 @@
1
+ # /clancy:update
2
+
3
+ Update Clancy itself to the latest version via npx and show what changed.
4
+
5
+ This re-runs the installer, which copies the latest command files from the npm package into your .claude/commands/clancy/ directory. Your .clancy/ project folder (scripts, docs, progress log) is never touched.
6
+
7
+ @.claude/clancy/workflows/update.md
8
+
9
+ Run the update as documented in the workflow above.
@@ -0,0 +1,43 @@
1
+ # Clancy — GitHub Issues configuration
2
+ # Copy this file to .env and fill in your values.
3
+ # Never commit .env to version control.
4
+
5
+ # ─── GitHub Issues ────────────────────────────────────────────────────────────
6
+ GITHUB_TOKEN=ghp_your-personal-access-token
7
+ GITHUB_REPO=owner/repo-name
8
+
9
+ # Optional: only pick up issues with this label (in addition to 'clancy').
10
+ # Useful for mixed backlogs where not every issue is suitable for autonomous implementation.
11
+ # Create the label in GitHub first, then add it to any issue you want Clancy to pick up.
12
+ # CLANCY_LABEL=clancy
13
+
14
+ # ─── Git ──────────────────────────────────────────────────────────────────────
15
+ # Base integration branch. Clancy branches from here when an issue has no milestone.
16
+ # When an issue has a milestone, Clancy auto-creates milestone/{slug} from this branch.
17
+ CLANCY_BASE_BRANCH=main
18
+
19
+ # ─── Loop ─────────────────────────────────────────────────────────────────────
20
+ # Max tickets to process per /clancy:run session (default: 20)
21
+ MAX_ITERATIONS=20
22
+
23
+ # ─── Model ────────────────────────────────────────────────────────────────────
24
+ # Claude model used for each ticket session. Leave unset to use the default.
25
+ # Options: claude-opus-4-6 | claude-sonnet-4-6 | claude-haiku-4-5
26
+ # CLANCY_MODEL=claude-sonnet-4-6
27
+
28
+ # ─── Optional: Figma MCP ──────────────────────────────────────────────────────
29
+ # Fetch design specs from Figma when a ticket has a Figma URL in its description
30
+ # FIGMA_API_KEY=your-figma-api-key
31
+
32
+ # ─── Optional: Playwright visual checks ───────────────────────────────────────
33
+ # Run a visual check after implementing UI tickets
34
+ # PLAYWRIGHT_ENABLED=true
35
+ # PLAYWRIGHT_DEV_COMMAND="yarn dev"
36
+ # PLAYWRIGHT_DEV_PORT=5173
37
+ # PLAYWRIGHT_STORYBOOK_COMMAND="yarn storybook"
38
+ # PLAYWRIGHT_STORYBOOK_PORT=6006
39
+ # PLAYWRIGHT_STARTUP_WAIT=15
40
+
41
+ # ─── Optional: Notifications ──────────────────────────────────────────────────
42
+ # Webhook URL for Slack or Teams notifications on ticket completion
43
+ # CLANCY_NOTIFY_WEBHOOK=https://hooks.slack.com/services/your/webhook/url
@@ -0,0 +1,53 @@
1
+ # Clancy — Jira configuration
2
+ # Copy this file to .env and fill in your values.
3
+ # Never commit .env to version control.
4
+
5
+ # ─── Jira ─────────────────────────────────────────────────────────────────────
6
+ JIRA_BASE_URL=https://your-org.atlassian.net
7
+ JIRA_USER=your-email@example.com
8
+ JIRA_API_TOKEN=your-api-token-from-id.atlassian.com
9
+ JIRA_PROJECT_KEY=PROJ
10
+
11
+ # Status name for "ready to be picked up" (default: To Do)
12
+ # Must be quoted if the status name contains spaces (e.g. "Selected for Development")
13
+ CLANCY_JQL_STATUS="To Do"
14
+
15
+ # Set to any non-empty value to filter by open sprints (requires Jira Software)
16
+ # Remove or leave empty if your project doesn't use sprints
17
+ # CLANCY_JQL_SPRINT=true
18
+
19
+ # Optional: only pick up tickets with this label. Recommended for mixed backlogs
20
+ # where not every ticket is suitable for autonomous implementation (e.g. non-code tasks).
21
+ # Create the label in Jira first, then add it to any ticket you want Clancy to pick up.
22
+ # CLANCY_LABEL="clancy"
23
+
24
+ # ─── Git ──────────────────────────────────────────────────────────────────────
25
+ # Base integration branch. Clancy branches from here when a ticket has no parent epic.
26
+ # When a ticket has a parent epic, Clancy auto-creates epic/{key} from this branch.
27
+ CLANCY_BASE_BRANCH=main
28
+
29
+ # ─── Loop ─────────────────────────────────────────────────────────────────────
30
+ # Max tickets to process per /clancy:run session (default: 5)
31
+ MAX_ITERATIONS=5
32
+
33
+ # ─── Model ────────────────────────────────────────────────────────────────────
34
+ # Claude model used for each ticket session. Leave unset to use the default.
35
+ # Options: claude-opus-4-6 | claude-sonnet-4-6 | claude-haiku-4-5
36
+ # CLANCY_MODEL=claude-sonnet-4-6
37
+
38
+ # ─── Optional: Figma MCP ──────────────────────────────────────────────────────
39
+ # Fetch design specs from Figma when a ticket has a Figma URL in its description
40
+ # FIGMA_API_KEY=your-figma-api-key
41
+
42
+ # ─── Optional: Playwright visual checks ───────────────────────────────────────
43
+ # Run a visual check after implementing UI tickets
44
+ # PLAYWRIGHT_ENABLED=true
45
+ # PLAYWRIGHT_DEV_COMMAND="yarn dev"
46
+ # PLAYWRIGHT_DEV_PORT=5173
47
+ # PLAYWRIGHT_STORYBOOK_COMMAND="yarn storybook"
48
+ # PLAYWRIGHT_STORYBOOK_PORT=6006
49
+ # PLAYWRIGHT_STARTUP_WAIT=15
50
+
51
+ # ─── Optional: Notifications ──────────────────────────────────────────────────
52
+ # Webhook URL for Slack or Teams notifications on ticket completion
53
+ # CLANCY_NOTIFY_WEBHOOK=https://hooks.slack.com/services/your/webhook/url
@@ -0,0 +1,43 @@
1
+ # Clancy — Linear configuration
2
+ # Copy this file to .env and fill in your values.
3
+ # Never commit .env to version control.
4
+
5
+ # ─── Linear ───────────────────────────────────────────────────────────────────
6
+ LINEAR_API_KEY=lin_api_your-personal-api-key
7
+ LINEAR_TEAM_ID=your-team-uuid
8
+
9
+ # Optional: only pick up issues with this label. Recommended for mixed backlogs
10
+ # where not every issue is suitable for autonomous implementation (e.g. non-code tasks).
11
+ # Create the label in Linear first, then add it to any issue you want Clancy to pick up.
12
+ # CLANCY_LABEL=clancy
13
+
14
+ # ─── Git ──────────────────────────────────────────────────────────────────────
15
+ # Base integration branch. Clancy branches from here when an issue has no parent.
16
+ # When an issue has a parent, Clancy auto-creates epic/{key} from this branch.
17
+ CLANCY_BASE_BRANCH=main
18
+
19
+ # ─── Loop ─────────────────────────────────────────────────────────────────────
20
+ # Max tickets to process per /clancy:run session (default: 20)
21
+ MAX_ITERATIONS=20
22
+
23
+ # ─── Model ────────────────────────────────────────────────────────────────────
24
+ # Claude model used for each ticket session. Leave unset to use the default.
25
+ # Options: claude-opus-4-6 | claude-sonnet-4-6 | claude-haiku-4-5
26
+ # CLANCY_MODEL=claude-sonnet-4-6
27
+
28
+ # ─── Optional: Figma MCP ──────────────────────────────────────────────────────
29
+ # Fetch design specs from Figma when a ticket has a Figma URL in its description
30
+ # FIGMA_API_KEY=your-figma-api-key
31
+
32
+ # ─── Optional: Playwright visual checks ───────────────────────────────────────
33
+ # Run a visual check after implementing UI tickets
34
+ # PLAYWRIGHT_ENABLED=true
35
+ # PLAYWRIGHT_DEV_COMMAND="yarn dev"
36
+ # PLAYWRIGHT_DEV_PORT=5173
37
+ # PLAYWRIGHT_STORYBOOK_COMMAND="yarn storybook"
38
+ # PLAYWRIGHT_STORYBOOK_PORT=6006
39
+ # PLAYWRIGHT_STARTUP_WAIT=15
40
+
41
+ # ─── Optional: Notifications ──────────────────────────────────────────────────
42
+ # Webhook URL for Slack or Teams notifications on ticket completion
43
+ # CLANCY_NOTIFY_WEBHOOK=https://hooks.slack.com/services/your/webhook/url
@@ -0,0 +1,69 @@
1
+ <!-- clancy:start -->
2
+ ## Clancy
3
+
4
+ This project uses Clancy for autonomous ticket-driven development.
5
+
6
+ ### Docs
7
+ Before every run, read all docs in `.clancy/docs/`:
8
+ - STACK.md — tech stack and dependencies
9
+ - INTEGRATIONS.md — external services and APIs
10
+ - ARCHITECTURE.md — system design and data flow
11
+ - CONVENTIONS.md — code style and patterns
12
+ - TESTING.md — test approach and coverage expectations
13
+ - GIT.md — branching, commit format, merge strategy
14
+ - DESIGN-SYSTEM.md — tokens, components, visual conventions
15
+ - ACCESSIBILITY.md — WCAG requirements and ARIA patterns
16
+ - DEFINITION-OF-DONE.md — checklist before marking a ticket complete
17
+ - CONCERNS.md — known risks, tech debt, things to avoid
18
+
19
+ ### Executability check
20
+
21
+ Before any git operation, branch creation, or code change — assess whether this ticket can be implemented entirely as a code change committed to this repo.
22
+
23
+ **Skip the ticket** if it primarily requires any of the following — Clancy cannot do these:
24
+ - **External system admin:** work in Google Analytics, Salesforce, HubSpot, the AWS console, app store dashboards, or any external platform not accessible through code
25
+ - **Human process steps:** getting sign-off or approval, sending emails to customers, coordinating with people, scheduling meetings, making announcements to users
26
+ - **Non-repo production ops:** deploying to production, rotating secrets in prod, scaling infrastructure — unless the task is purely about editing CI/CD config files that live in this repo
27
+ - **Non-code deliverables:** writing runbooks, updating Confluence or wikis, creating presentations, documenting in external tools
28
+
29
+ When in doubt: "Is the primary deliverable a code change committed to this repo?" — if yes, implement it; if no, skip it.
30
+
31
+ **If skipping, do all four of these in order:**
32
+ 1. Output this exact line: `⚠ Skipping [TICKET-KEY]: {one-line reason}`
33
+ 2. Output this exact line: `Ticket skipped — update it to be codebase-only work, then re-run.`
34
+ 3. Append to `.clancy/progress.txt`: `YYYY-MM-DD HH:MM | TICKET-KEY | SKIPPED | {reason}`
35
+ 4. Stop. No branches, no file changes, no git operations.
36
+
37
+ **If the ticket is codebase work**, proceed to implementation normally.
38
+
39
+ ### Git workflow
40
+ - Read GIT.md before every run — follow its conventions exactly
41
+ - Default (if GIT.md is silent): one feature branch per ticket `feature/{ticket-key-lowercase}`, squash merge into target branch, conventional commits `feat(TICKET-123): summary`
42
+ - Target branch is auto-detected from the ticket: if it has a parent epic, Clancy branches from and merges into `epic/{epic-key}` (created from `CLANCY_BASE_BRANCH` if it doesn't exist); otherwise branches from `CLANCY_BASE_BRANCH` directly
43
+ - Delete ticket branch locally after merge — never push deletes
44
+
45
+ ### Progress
46
+ Log completed tickets to `.clancy/progress.txt`:
47
+ `YYYY-MM-DD HH:MM | TICKET-KEY | Summary | DONE`
48
+
49
+ ### Design context
50
+ When a ticket description contains a Figma URL, fetch design context before implementing.
51
+ Use the three-tier approach in order:
52
+ 1. Figma MCP: get_metadata → targeted get_design_context → get_screenshot (3 calls, best quality)
53
+ 2. Figma REST API image export: parse file key + node ID from URL, fetch rendered PNG as vision input
54
+ 3. Ticket image attachment: use any image attached to the ticket description as vision input
55
+ Fetch once only — never return to Figma mid-session.
56
+ Map all token values to CSS custom properties. Never use raw SVGs or hardcoded colours.
57
+ Figma URL must come from the ticket description only — never from CLAUDE.md or other docs.
58
+
59
+ ### Visual checks
60
+ After implementing a UI ticket, run a visual check before committing.
61
+ Read .clancy/docs/PLAYWRIGHT.md to determine which server to use (Storybook or dev server).
62
+ Apply the decision rule against this ticket's description — route/page/screen/layout → dev server,
63
+ component/atom/molecule/organism/variant/story → Storybook, ambiguous → dev server default.
64
+ Start the server using health check polling (not sleep). Navigate to the relevant route or story.
65
+ Screenshot, assess visually, check console. Fix before committing if anything looks wrong.
66
+ If a Figma design was fetched for this ticket, compare the screenshot directly against it — check layout, spacing, colours, typography, and component fidelity. Treat the Figma design as the source of truth.
67
+ Kill by PID then sweep the port unconditionally — never leave a port open.
68
+ Log result: YYYY-MM-DD HH:MM | TICKET-KEY | PLAYWRIGHT_PASS|FAIL | server-used
69
+ <!-- clancy:end -->
@@ -0,0 +1,111 @@
1
+ #!/usr/bin/env bash
2
+ # Strict mode: exit on error (-e), undefined variables (-u), pipe failures (-o pipefail).
3
+ # This means any command that fails will stop the script immediately rather than silently continuing.
4
+ set -euo pipefail
5
+
6
+ # ─── WHAT THIS SCRIPT DOES ─────────────────────────────────────────────────────
7
+ #
8
+ # Loop runner for Clancy. Calls clancy-once.sh repeatedly until:
9
+ # - No more tickets are found ("No tickets found", "All done", etc.)
10
+ # - A preflight check fails (output line starting with ✗)
11
+ # - MAX_ITERATIONS is reached
12
+ # - The user presses Ctrl+C
13
+ #
14
+ # This script does not know about boards. All board logic lives in clancy-once.sh,
15
+ # which is always the runtime filename regardless of which board is configured.
16
+ # /clancy:init copies the correct board variant as clancy-once.sh during setup.
17
+ #
18
+ # ───────────────────────────────────────────────────────────────────────────────
19
+
20
+ # ─── PREFLIGHT ─────────────────────────────────────────────────────────────────
21
+
22
+ command -v claude >/dev/null 2>&1 || {
23
+ echo "✗ claude CLI not found."
24
+ echo " Install it: https://claude.ai/code"
25
+ exit 0
26
+ }
27
+ command -v jq >/dev/null 2>&1 || {
28
+ echo "✗ jq not found."
29
+ echo " Install: brew install jq (mac) | apt install jq (linux)"
30
+ exit 0
31
+ }
32
+ command -v curl >/dev/null 2>&1 || {
33
+ echo "✗ curl not found. Install curl for your OS."
34
+ exit 0
35
+ }
36
+
37
+ [ -f .clancy/.env ] || {
38
+ echo "✗ .clancy/.env not found."
39
+ echo " Copy .clancy/.env.example to .clancy/.env and fill in your credentials."
40
+ exit 0
41
+ }
42
+ # shellcheck source=/dev/null
43
+ source .clancy/.env
44
+
45
+ git rev-parse --git-dir >/dev/null 2>&1 || {
46
+ echo "✗ Not a git repository."
47
+ echo " Clancy must be run from the root of a git project."
48
+ exit 0
49
+ }
50
+
51
+ # ─── END PREFLIGHT ─────────────────────────────────────────────────────────────
52
+
53
+ MAX_ITERATIONS=${MAX_ITERATIONS:-5}
54
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
55
+
56
+ # clancy-once.sh is always the runtime filename regardless of board.
57
+ # /clancy:init copies the correct board variant as clancy-once.sh.
58
+ ONCE_SCRIPT="$SCRIPT_DIR/clancy-once.sh"
59
+
60
+ if [ ! -f "$ONCE_SCRIPT" ]; then
61
+ echo "✗ Script not found: $ONCE_SCRIPT"
62
+ echo " Run /clancy:init to scaffold scripts."
63
+ exit 0
64
+ fi
65
+
66
+ echo "Starting Clancy — will process up to $MAX_ITERATIONS ticket(s). Ctrl+C to stop early."
67
+ echo ""
68
+
69
+ i=0
70
+ while [ "$i" -lt "$MAX_ITERATIONS" ]; do
71
+ i=$((i + 1))
72
+ echo ""
73
+ echo "=== Iteration $i of $MAX_ITERATIONS ==="
74
+
75
+ # Run clancy-once.sh and stream its output live via tee.
76
+ # tee writes to both stdout (visible to user) and a temp file (for stop-condition checks).
77
+ # Without tee, output would be buffered in a variable and hidden during implementation.
78
+ TMPFILE=$(mktemp)
79
+ bash "$ONCE_SCRIPT" 2>&1 | tee "$TMPFILE"
80
+ OUTPUT=$(cat "$TMPFILE")
81
+ rm -f "$TMPFILE"
82
+
83
+ # Stop if no tickets remain
84
+ if echo "$OUTPUT" | grep -qE "No tickets found|No issues found|All done"; then
85
+ echo ""
86
+ echo "✓ Clancy finished — no more tickets."
87
+ exit 0
88
+ fi
89
+
90
+ # Stop if Claude skipped the ticket (not implementable from the codebase).
91
+ # Re-running would just fetch and skip the same ticket again — stop and let
92
+ # the user update the ticket or remove it from the queue before continuing.
93
+ if echo "$OUTPUT" | grep -q "Ticket skipped"; then
94
+ echo ""
95
+ echo "⚠ Clancy stopped — ticket was skipped (not implementable from the codebase)."
96
+ echo " Update the ticket to focus on codebase work, then re-run."
97
+ exit 0
98
+ fi
99
+
100
+ # Stop if a preflight check failed (lines starting with ✗)
101
+ if echo "$OUTPUT" | grep -qE "^✗ "; then
102
+ echo ""
103
+ echo "✗ Clancy stopped — preflight check failed. See output above."
104
+ exit 0
105
+ fi
106
+
107
+ sleep 2
108
+ done
109
+
110
+ echo ""
111
+ echo "Reached max iterations ($MAX_ITERATIONS). Run clancy-afk.sh again to continue."
@@ -0,0 +1,225 @@
1
+ #!/usr/bin/env bash
2
+ # Strict mode: exit on error (-e), undefined variables (-u), pipe failures (-o pipefail).
3
+ # This means any command that fails will stop the script immediately rather than silently continuing.
4
+ set -euo pipefail
5
+
6
+ # ─── WHAT THIS SCRIPT DOES ─────────────────────────────────────────────────────
7
+ #
8
+ # Board: GitHub Issues
9
+ #
10
+ # 1. Preflight — checks all required tools, credentials, and repo reachability
11
+ # 2. Fetch — pulls the next open issue with the 'clancy' label assigned to you
12
+ # 3. Branch — creates a feature branch from the issue's milestone branch (or base branch)
13
+ # 4. Implement — passes the issue to Claude Code, which reads .clancy/docs/ and implements it
14
+ # 5. Merge — squash-merges the feature branch back into the target branch
15
+ # 6. Close — marks the GitHub issue as closed via the API
16
+ # 7. Log — appends a completion entry to .clancy/progress.txt
17
+ #
18
+ # This script is run once per issue. The loop is handled by clancy-afk.sh.
19
+ #
20
+ # NOTE: GitHub's /issues endpoint returns pull requests too. This script filters
21
+ # them out by checking for the presence of the 'pull_request' key in each result.
22
+ #
23
+ # NOTE: Failures use exit 0, not exit 1. This is intentional — clancy-afk.sh
24
+ # detects stop conditions by reading script output rather than exit codes, so a
25
+ # non-zero exit would be treated as an unexpected crash rather than a clean stop.
26
+ #
27
+ # ───────────────────────────────────────────────────────────────────────────────
28
+
29
+ # ─── PREFLIGHT ─────────────────────────────────────────────────────────────────
30
+
31
+ command -v claude >/dev/null 2>&1 || {
32
+ echo "✗ claude CLI not found."
33
+ echo " Install it: https://claude.ai/code"
34
+ exit 0
35
+ }
36
+ command -v jq >/dev/null 2>&1 || {
37
+ echo "✗ jq not found."
38
+ echo " Install: brew install jq (mac) | apt install jq (linux)"
39
+ exit 0
40
+ }
41
+ command -v curl >/dev/null 2>&1 || {
42
+ echo "✗ curl not found. Install curl for your OS."
43
+ exit 0
44
+ }
45
+
46
+ [ -f .clancy/.env ] || {
47
+ echo "✗ .clancy/.env not found."
48
+ echo " Copy .clancy/.env.example to .clancy/.env and fill in your credentials."
49
+ echo " Then run: /clancy:init"
50
+ exit 0
51
+ }
52
+ # shellcheck source=/dev/null
53
+ source .clancy/.env
54
+
55
+ git rev-parse --git-dir >/dev/null 2>&1 || {
56
+ echo "✗ Not a git repository."
57
+ echo " Clancy must be run from the root of a git project."
58
+ exit 0
59
+ }
60
+
61
+ if ! git diff --quiet || ! git diff --cached --quiet; then
62
+ echo "⚠ Working directory has uncommitted changes."
63
+ echo " Consider stashing or committing first to avoid confusion."
64
+ fi
65
+
66
+ [ -n "${GITHUB_TOKEN:-}" ] || { echo "✗ GITHUB_TOKEN is not set in .clancy/.env"; exit 0; }
67
+ [ -n "${GITHUB_REPO:-}" ] || { echo "✗ GITHUB_REPO is not set in .clancy/.env"; exit 0; }
68
+
69
+ # Validate GITHUB_REPO format — must be owner/repo with safe characters only
70
+ if ! echo "$GITHUB_REPO" | grep -qE '^[a-zA-Z0-9_.-]+/[a-zA-Z0-9_.-]+$'; then
71
+ echo "✗ GITHUB_REPO format is invalid. Expected owner/repo (e.g. acme/my-app). Check GITHUB_REPO in .clancy/.env."
72
+ exit 0
73
+ fi
74
+
75
+ PING=$(curl -s -o /dev/null -w "%{http_code}" \
76
+ -H "Authorization: Bearer $GITHUB_TOKEN" \
77
+ -H "X-GitHub-Api-Version: 2022-11-28" \
78
+ "https://api.github.com/repos/$GITHUB_REPO")
79
+
80
+ case "$PING" in
81
+ 200) ;;
82
+ 401) echo "✗ GitHub authentication failed. Check GITHUB_TOKEN in .clancy/.env."; exit 0 ;;
83
+ 403) echo "✗ GitHub access denied. Your token may lack the repo scope."; exit 0 ;;
84
+ 404) echo "✗ GitHub repo '$GITHUB_REPO' not found. Check GITHUB_REPO in .clancy/.env."; exit 0 ;;
85
+ 000) echo "✗ Could not reach GitHub. Check your network."; exit 0 ;;
86
+ *) echo "✗ GitHub returned unexpected status $PING. Check your config."; exit 0 ;;
87
+ esac
88
+
89
+ if [ "${PLAYWRIGHT_ENABLED:-}" = "true" ]; then
90
+ if lsof -ti:"${PLAYWRIGHT_DEV_PORT:-5173}" >/dev/null 2>&1; then
91
+ echo "⚠ Port ${PLAYWRIGHT_DEV_PORT:-5173} is already in use."
92
+ echo " If visual checks fail, stop whatever is using the port first."
93
+ fi
94
+ fi
95
+
96
+ echo "✓ Preflight passed. Starting Clancy..."
97
+
98
+ # ─── END PREFLIGHT ─────────────────────────────────────────────────────────────
99
+
100
+ # ─── FETCH ISSUE ───────────────────────────────────────────────────────────────
101
+
102
+ # Fetch open issues assigned to the authenticated user with the 'clancy' label.
103
+ # GitHub's issues endpoint returns PRs too — filter them out by checking for pull_request key.
104
+ # per_page=3 so we can find one real issue even if the first result(s) are PRs.
105
+ RESPONSE=$(curl -s \
106
+ -H "Authorization: Bearer $GITHUB_TOKEN" \
107
+ -H "X-GitHub-Api-Version: 2022-11-28" \
108
+ "https://api.github.com/repos/$GITHUB_REPO/issues?state=open&assignee=@me&labels=clancy&per_page=3")
109
+
110
+ # Verify response is an array before parsing (guards against error objects on rate limit / transient failure)
111
+ if ! echo "$RESPONSE" | jq -e 'type == "array"' >/dev/null 2>&1; then
112
+ ERR_MSG=$(echo "$RESPONSE" | jq -r '.message // "Unexpected response"' 2>/dev/null || echo "Unexpected response")
113
+ echo "✗ GitHub API error: $ERR_MSG. Check GITHUB_TOKEN in .clancy/.env."
114
+ exit 0
115
+ fi
116
+
117
+ # Filter out PRs and take first real issue
118
+ ISSUE=$(echo "$RESPONSE" | jq 'map(select(has("pull_request") | not)) | .[0]')
119
+
120
+ if [ "$(echo "$ISSUE" | jq 'type')" = '"null"' ] || [ -z "$(echo "$ISSUE" | jq -r '.number // empty')" ]; then
121
+ echo "No issues found. All done!"
122
+ exit 0
123
+ fi
124
+
125
+ ISSUE_NUMBER=$(echo "$ISSUE" | jq -r '.number')
126
+ TITLE=$(echo "$ISSUE" | jq -r '.title')
127
+ BODY=$(echo "$ISSUE" | jq -r '.body // "No description"')
128
+ MILESTONE=$(echo "$ISSUE" | jq -r '.milestone.title // "none"')
129
+
130
+ BASE_BRANCH="${CLANCY_BASE_BRANCH:-main}"
131
+ TICKET_BRANCH="feature/issue-${ISSUE_NUMBER}"
132
+
133
+ # GitHub has no native epic concept — use milestone as the grouping signal.
134
+ # If the issue has a milestone, branch from milestone/{slug} (creating it from
135
+ # BASE_BRANCH if it doesn't exist yet). Otherwise branch from BASE_BRANCH directly.
136
+ if [ "$MILESTONE" != "none" ]; then
137
+ MILESTONE_SLUG=$(echo "$MILESTONE" | tr '[:upper:]' '[:lower:]' | tr ' ' '-' | tr -cd '[:alnum:]-')
138
+ TARGET_BRANCH="milestone/${MILESTONE_SLUG}"
139
+ git show-ref --verify --quiet "refs/heads/$TARGET_BRANCH" \
140
+ || git checkout -b "$TARGET_BRANCH" "$BASE_BRANCH"
141
+ else
142
+ TARGET_BRANCH="$BASE_BRANCH"
143
+ fi
144
+
145
+ # ─── IMPLEMENT ─────────────────────────────────────────────────────────────────
146
+
147
+ echo "Picking up: [#${ISSUE_NUMBER}] $TITLE"
148
+ echo "Milestone: $MILESTONE | Target branch: $TARGET_BRANCH"
149
+
150
+ git checkout "$TARGET_BRANCH"
151
+ # -B creates the branch if it doesn't exist, or resets it to HEAD if it does.
152
+ # This handles retries cleanly without failing on an already-existing branch.
153
+ git checkout -B "$TICKET_BRANCH"
154
+
155
+ PROMPT="You are implementing GitHub Issue #${ISSUE_NUMBER}.
156
+
157
+ Title: $TITLE
158
+ Milestone: $MILESTONE
159
+
160
+ Description:
161
+ $BODY
162
+
163
+ Step 0 — Executability check (do this before any git or file operation):
164
+ Read the issue title and description above. Can this issue be implemented entirely
165
+ as a code change committed to this repo? Consult the 'Executability check' section of
166
+ CLAUDE.md for the full list of skip conditions.
167
+
168
+ If you must SKIP this issue:
169
+ 1. Output: ⚠ Skipping [#${ISSUE_NUMBER}]: {one-line reason}
170
+ 2. Output: Ticket skipped — update it to be codebase-only work, then re-run.
171
+ 3. Append to .clancy/progress.txt: YYYY-MM-DD HH:MM | #${ISSUE_NUMBER} | SKIPPED | {reason}
172
+ 4. Stop — no branches, no file changes, no git operations.
173
+
174
+ If the issue IS implementable, continue:
175
+ 1. Read ALL docs in .clancy/docs/ — especially GIT.md for branching and commit conventions
176
+ 2. Follow the conventions in GIT.md exactly
177
+ 3. Implement the issue fully
178
+ 4. Commit your work following the conventions in GIT.md
179
+ 5. When done, confirm you are finished."
180
+
181
+ CLAUDE_ARGS=(--dangerously-skip-permissions)
182
+ [ -n "${CLANCY_MODEL:-}" ] && CLAUDE_ARGS+=(--model "$CLANCY_MODEL")
183
+ echo "$PROMPT" | claude "${CLAUDE_ARGS[@]}"
184
+
185
+ # ─── MERGE, CLOSE & LOG ────────────────────────────────────────────────────────
186
+
187
+ # Squash all commits from the feature branch into a single commit on the target branch.
188
+ git checkout "$TARGET_BRANCH"
189
+ git merge --squash "$TICKET_BRANCH"
190
+ if git diff --cached --quiet; then
191
+ echo "⚠ No changes staged after squash merge. Claude may not have committed any work."
192
+ else
193
+ git commit -m "feat(#${ISSUE_NUMBER}): $TITLE"
194
+ fi
195
+
196
+ # Delete ticket branch locally
197
+ git branch -d "$TICKET_BRANCH"
198
+
199
+ # Close the issue — warn but don't fail if this doesn't go through
200
+ CLOSE_HTTP=$(curl -s -o /dev/null -w "%{http_code}" -X PATCH \
201
+ -H "Authorization: Bearer $GITHUB_TOKEN" \
202
+ -H "X-GitHub-Api-Version: 2022-11-28" \
203
+ -H "Content-Type: application/json" \
204
+ "https://api.github.com/repos/$GITHUB_REPO/issues/${ISSUE_NUMBER}" \
205
+ -d '{"state": "closed"}')
206
+ [ "$CLOSE_HTTP" = "200" ] || echo "⚠ Could not close issue #${ISSUE_NUMBER} (HTTP $CLOSE_HTTP). Close it manually on GitHub."
207
+
208
+ # Log progress
209
+ echo "$(date '+%Y-%m-%d %H:%M') | #${ISSUE_NUMBER} | $TITLE | DONE" >> .clancy/progress.txt
210
+
211
+ echo "✓ #${ISSUE_NUMBER} complete."
212
+
213
+ # Send completion notification if webhook is configured
214
+ if [ -n "${CLANCY_NOTIFY_WEBHOOK:-}" ]; then
215
+ NOTIFY_MSG="✓ Clancy completed [#${ISSUE_NUMBER}] $TITLE"
216
+ if echo "$CLANCY_NOTIFY_WEBHOOK" | grep -q "hooks.slack.com"; then
217
+ curl -s -X POST "$CLANCY_NOTIFY_WEBHOOK" \
218
+ -H "Content-Type: application/json" \
219
+ -d "$(jq -n --arg text "$NOTIFY_MSG" '{"text": $text}')" >/dev/null 2>&1 || true
220
+ else
221
+ curl -s -X POST "$CLANCY_NOTIFY_WEBHOOK" \
222
+ -H "Content-Type: application/json" \
223
+ -d "$(jq -n --arg text "$NOTIFY_MSG" '{"type":"message","attachments":[{"contentType":"application/vnd.microsoft.card.adaptive","content":{"type":"AdaptiveCard","body":[{"type":"TextBlock","text":$text}]}}]}')" >/dev/null 2>&1 || true
224
+ fi
225
+ fi