chief-clancy 0.2.0 → 0.3.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/README.md +13 -24
- package/dist/installer/file-ops/file-ops.d.ts +32 -0
- package/dist/installer/file-ops/file-ops.d.ts.map +1 -0
- package/dist/installer/file-ops/file-ops.js +58 -0
- package/dist/installer/file-ops/file-ops.js.map +1 -0
- package/dist/installer/hook-installer/hook-installer.d.ts +29 -0
- package/dist/installer/hook-installer/hook-installer.d.ts.map +1 -0
- package/dist/installer/hook-installer/hook-installer.js +96 -0
- package/dist/installer/hook-installer/hook-installer.js.map +1 -0
- package/dist/installer/install.d.ts +3 -0
- package/dist/installer/install.d.ts.map +1 -0
- package/dist/installer/install.js +227 -0
- package/dist/installer/install.js.map +1 -0
- package/dist/installer/manifest/manifest.d.ts +41 -0
- package/dist/installer/manifest/manifest.d.ts.map +1 -0
- package/dist/installer/manifest/manifest.js +97 -0
- package/dist/installer/manifest/manifest.js.map +1 -0
- package/dist/installer/prompts/prompts.d.ts +33 -0
- package/dist/installer/prompts/prompts.d.ts.map +1 -0
- package/dist/installer/prompts/prompts.js +55 -0
- package/dist/installer/prompts/prompts.js.map +1 -0
- package/dist/schemas/env.d.ts +75 -0
- package/dist/schemas/env.d.ts.map +1 -0
- package/dist/schemas/env.js +40 -0
- package/dist/schemas/env.js.map +1 -0
- package/dist/schemas/github.d.ts +27 -0
- package/dist/schemas/github.d.ts.map +1 -0
- package/dist/schemas/github.js +17 -0
- package/dist/schemas/github.js.map +1 -0
- package/dist/schemas/index.d.ts +9 -0
- package/dist/schemas/index.d.ts.map +1 -0
- package/dist/schemas/index.js +5 -0
- package/dist/schemas/index.js.map +1 -0
- package/dist/schemas/jira.d.ts +37 -0
- package/dist/schemas/jira.d.ts.map +1 -0
- package/dist/schemas/jira.js +37 -0
- package/dist/schemas/jira.js.map +1 -0
- package/dist/schemas/linear.d.ts +67 -0
- package/dist/schemas/linear.d.ts.map +1 -0
- package/dist/schemas/linear.js +50 -0
- package/dist/schemas/linear.js.map +1 -0
- package/dist/scripts/afk/afk.d.ts +21 -0
- package/dist/scripts/afk/afk.d.ts.map +1 -0
- package/dist/scripts/afk/afk.js +116 -0
- package/dist/scripts/afk/afk.js.map +1 -0
- package/dist/scripts/board/github/github.d.ts +56 -0
- package/dist/scripts/board/github/github.d.ts.map +1 -0
- package/dist/scripts/board/github/github.js +142 -0
- package/dist/scripts/board/github/github.js.map +1 -0
- package/dist/scripts/board/jira/jira.d.ts +90 -0
- package/dist/scripts/board/jira/jira.d.ts.map +1 -0
- package/dist/scripts/board/jira/jira.js +251 -0
- package/dist/scripts/board/jira/jira.js.map +1 -0
- package/dist/scripts/board/linear/linear.d.ts +85 -0
- package/dist/scripts/board/linear/linear.d.ts.map +1 -0
- package/dist/scripts/board/linear/linear.js +209 -0
- package/dist/scripts/board/linear/linear.js.map +1 -0
- package/dist/scripts/once/once.d.ts +12 -0
- package/dist/scripts/once/once.d.ts.map +1 -0
- package/dist/scripts/once/once.js +323 -0
- package/dist/scripts/once/once.js.map +1 -0
- package/dist/scripts/shared/branch/branch.d.ts +50 -0
- package/dist/scripts/shared/branch/branch.d.ts.map +1 -0
- package/dist/scripts/shared/branch/branch.js +61 -0
- package/dist/scripts/shared/branch/branch.js.map +1 -0
- package/dist/scripts/shared/claude-cli/claude-cli.d.ts +17 -0
- package/dist/scripts/shared/claude-cli/claude-cli.d.ts.map +1 -0
- package/dist/scripts/shared/claude-cli/claude-cli.js +35 -0
- package/dist/scripts/shared/claude-cli/claude-cli.js.map +1 -0
- package/dist/scripts/shared/env-parser/env-parser.d.ts +30 -0
- package/dist/scripts/shared/env-parser/env-parser.d.ts.map +1 -0
- package/dist/scripts/shared/env-parser/env-parser.js +64 -0
- package/dist/scripts/shared/env-parser/env-parser.js.map +1 -0
- package/dist/scripts/shared/env-schema/env-schema.d.ts +27 -0
- package/dist/scripts/shared/env-schema/env-schema.d.ts.map +1 -0
- package/dist/scripts/shared/env-schema/env-schema.js +46 -0
- package/dist/scripts/shared/env-schema/env-schema.js.map +1 -0
- package/dist/scripts/shared/git-ops/git-ops.d.ts +52 -0
- package/dist/scripts/shared/git-ops/git-ops.d.ts.map +1 -0
- package/dist/scripts/shared/git-ops/git-ops.js +107 -0
- package/dist/scripts/shared/git-ops/git-ops.js.map +1 -0
- package/dist/scripts/shared/http/http.d.ts +52 -0
- package/dist/scripts/shared/http/http.d.ts.map +1 -0
- package/dist/scripts/shared/http/http.js +74 -0
- package/dist/scripts/shared/http/http.js.map +1 -0
- package/dist/scripts/shared/notify/notify.d.ts +46 -0
- package/dist/scripts/shared/notify/notify.d.ts.map +1 -0
- package/dist/scripts/shared/notify/notify.js +88 -0
- package/dist/scripts/shared/notify/notify.js.map +1 -0
- package/dist/scripts/shared/preflight/preflight.d.ts +40 -0
- package/dist/scripts/shared/preflight/preflight.d.ts.map +1 -0
- package/dist/scripts/shared/preflight/preflight.js +84 -0
- package/dist/scripts/shared/preflight/preflight.js.map +1 -0
- package/dist/scripts/shared/progress/progress.d.ts +25 -0
- package/dist/scripts/shared/progress/progress.d.ts.map +1 -0
- package/dist/scripts/shared/progress/progress.js +46 -0
- package/dist/scripts/shared/progress/progress.js.map +1 -0
- package/dist/scripts/shared/prompt/prompt.d.ts +38 -0
- package/dist/scripts/shared/prompt/prompt.d.ts.map +1 -0
- package/dist/scripts/shared/prompt/prompt.js +77 -0
- package/dist/scripts/shared/prompt/prompt.js.map +1 -0
- package/dist/types/board.d.ts +13 -0
- package/dist/types/board.d.ts.map +1 -0
- package/dist/types/board.js +5 -0
- package/dist/types/board.js.map +1 -0
- package/dist/types/index.d.ts +3 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +2 -0
- package/dist/types/index.js.map +1 -0
- package/dist/utils/ansi/ansi.d.ts +55 -0
- package/dist/utils/ansi/ansi.d.ts.map +1 -0
- package/dist/utils/ansi/ansi.js +55 -0
- package/dist/utils/ansi/ansi.js.map +1 -0
- package/dist/utils/index.d.ts +3 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/dist/utils/index.js +3 -0
- package/dist/utils/index.js.map +1 -0
- package/dist/utils/parse-json/parse-json.d.ts +20 -0
- package/dist/utils/parse-json/parse-json.d.ts.map +1 -0
- package/dist/utils/parse-json/parse-json.js +27 -0
- package/dist/utils/parse-json/parse-json.js.map +1 -0
- package/hooks/clancy-check-update.js +2 -2
- package/hooks/clancy-credential-guard.js +8 -1
- package/package.json +52 -8
- package/registry/boards.json +3 -6
- package/src/templates/CLAUDE.md +1 -1
- package/src/workflows/doctor.md +32 -23
- package/src/workflows/init.md +88 -19
- package/src/workflows/logs.md +13 -6
- package/src/workflows/map-codebase.md +17 -16
- package/src/workflows/once.md +22 -12
- package/src/workflows/review.md +40 -27
- package/src/workflows/run.md +20 -12
- package/src/workflows/scaffold.md +12 -1023
- package/src/workflows/settings.md +9 -6
- package/src/workflows/status.md +17 -8
- package/src/workflows/uninstall.md +11 -6
- package/src/workflows/update.md +13 -11
- package/bin/install.js +0 -362
- package/src/templates/scripts/clancy-afk.sh +0 -111
- package/src/templates/scripts/clancy-once-github.sh +0 -249
- package/src/templates/scripts/clancy-once-linear.sh +0 -320
- package/src/templates/scripts/clancy-once.sh +0 -322
|
@@ -34,7 +34,10 @@ Source `.clancy/.env` silently. Detect which board is configured:
|
|
|
34
34
|
Show all current values. Board-specific settings only appear when that board is configured.
|
|
35
35
|
|
|
36
36
|
```
|
|
37
|
-
Clancy
|
|
37
|
+
🚨 Clancy — Settings
|
|
38
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
39
|
+
|
|
40
|
+
"Welcome to headquarters." — .clancy/.env
|
|
38
41
|
|
|
39
42
|
General
|
|
40
43
|
[1] Max iterations {MAX_ITERATIONS:-5} tickets per /clancy:run session
|
|
@@ -72,7 +75,7 @@ Number each option sequentially. Show only the board-specific section that match
|
|
|
72
75
|
|
|
73
76
|
## Step 4 — Handle each selection
|
|
74
77
|
|
|
75
|
-
After the user picks a number, handle it as below. After saving, print
|
|
78
|
+
After the user picks a number, handle it as below. After saving, print `✅ Saved.` and loop back to Step 3 to show the updated menu.
|
|
76
79
|
|
|
77
80
|
---
|
|
78
81
|
|
|
@@ -344,7 +347,7 @@ Verify the new credentials before making any changes — same checks as the init
|
|
|
344
347
|
|
|
345
348
|
```
|
|
346
349
|
Verifying...
|
|
347
|
-
|
|
350
|
+
✅ Connected — {board-specific confirmation, e.g. "PROJ reachable" / "acme/my-app found" / "Linear authenticated"}
|
|
348
351
|
```
|
|
349
352
|
|
|
350
353
|
If verification fails, tell the user clearly and offer:
|
|
@@ -379,12 +382,12 @@ If no: print `Cancelled. No changes made.` and loop back to the menu.
|
|
|
379
382
|
- Linear: `LINEAR_API_KEY`, `LINEAR_TEAM_ID`
|
|
380
383
|
2. Write the new board credentials to `.clancy/.env`
|
|
381
384
|
3. If switching to Jira: also ask the status filter question (same as init Q3) and write `CLANCY_JQL_STATUS` to `.clancy/.env`
|
|
382
|
-
4.
|
|
385
|
+
4. No script replacement needed — the JS shims are board-agnostic (board detection happens at runtime from `.clancy/.env`)
|
|
383
386
|
|
|
384
387
|
Print:
|
|
385
388
|
|
|
386
389
|
```
|
|
387
|
-
|
|
390
|
+
✅ Switched to {new board}. "New beat, same Chief."
|
|
388
391
|
```
|
|
389
392
|
|
|
390
393
|
Then loop back to the main settings menu.
|
|
@@ -434,7 +437,7 @@ When selected:
|
|
|
434
437
|
}
|
|
435
438
|
```
|
|
436
439
|
|
|
437
|
-
3. Print:
|
|
440
|
+
3. Print: `✅ Defaults saved to ~/.clancy/defaults.json — new projects will inherit these settings.`
|
|
438
441
|
|
|
439
442
|
4. Loop back to the settings menu.
|
|
440
443
|
|
package/src/workflows/status.md
CHANGED
|
@@ -23,7 +23,7 @@ Detect board from `.clancy/.env`:
|
|
|
23
23
|
|
|
24
24
|
**Jira:**
|
|
25
25
|
|
|
26
|
-
Build the JQL string first using the same clauses as
|
|
26
|
+
Build the JQL string first using the same clauses as the once-runner:
|
|
27
27
|
- Sprint clause: include `AND sprint in openSprints()` if `CLANCY_JQL_SPRINT` is set
|
|
28
28
|
- Label clause: include `AND labels = "$CLANCY_LABEL"` if `CLANCY_LABEL` is set
|
|
29
29
|
- `CLANCY_JQL_STATUS` defaults to `To Do` if not set
|
|
@@ -76,7 +76,10 @@ query {
|
|
|
76
76
|
|
|
77
77
|
If tickets found, display:
|
|
78
78
|
```
|
|
79
|
-
|
|
79
|
+
🚨 Clancy — Status
|
|
80
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
81
|
+
|
|
82
|
+
Next up:
|
|
80
83
|
|
|
81
84
|
1. [{TICKET-KEY}] {Summary}
|
|
82
85
|
Epic: {epic key} — {epic title}
|
|
@@ -90,19 +93,25 @@ Next up for Clancy:
|
|
|
90
93
|
Epic: {epic key} — {epic title}
|
|
91
94
|
Status: {status}
|
|
92
95
|
|
|
93
|
-
Run /clancy:once to pick up the
|
|
94
|
-
Run /clancy:run to process all tickets in the queue.
|
|
96
|
+
"Let me check the dispatch..." — Run /clancy:once to pick up #1, or /clancy:run to process the queue.
|
|
95
97
|
```
|
|
96
98
|
|
|
97
99
|
If no tickets found:
|
|
98
100
|
```
|
|
99
|
-
|
|
100
|
-
|
|
101
|
+
🚨 Clancy — Status
|
|
102
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
103
|
+
|
|
104
|
+
No tickets found in the current queue.
|
|
105
|
+
|
|
106
|
+
"Quiet. Too quiet." — Check your board or run /clancy:init to verify your config.
|
|
101
107
|
```
|
|
102
108
|
|
|
103
109
|
If API call fails, show the error clearly:
|
|
104
110
|
```
|
|
105
|
-
|
|
111
|
+
🚨 Clancy — Status
|
|
112
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
113
|
+
|
|
114
|
+
❌ Board API error: {error message}
|
|
106
115
|
|
|
107
116
|
Tips:
|
|
108
117
|
- Check your credentials in .clancy/.env
|
|
@@ -117,4 +126,4 @@ Tips:
|
|
|
117
126
|
- Show up to 3 tickets. If only 1 or 2 are available, show those.
|
|
118
127
|
- Omit "Epic:" line if no epic/parent data is present for that ticket.
|
|
119
128
|
- This command is strictly read-only. No git ops, no file writes, no Claude invocation for analysis.
|
|
120
|
-
- The query used here must be identical to the one used by
|
|
129
|
+
- The query used here must be identical to the one used by the once-runner — what status shows is exactly what run would pick up.
|
|
@@ -29,6 +29,9 @@ Check both locations silently. Each install has two parts — commands and workf
|
|
|
29
29
|
Show exactly this message, filling in the detected location:
|
|
30
30
|
|
|
31
31
|
```
|
|
32
|
+
🚨 Clancy — Uninstall
|
|
33
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
34
|
+
|
|
32
35
|
This will remove Clancy's slash commands from [location].
|
|
33
36
|
Your .clancy/ folder will not be touched.
|
|
34
37
|
Continue? (yes / no)
|
|
@@ -45,7 +48,7 @@ Delete both the commands directory and the workflows directory for the chosen lo
|
|
|
45
48
|
- Project-local: `.claude/commands/clancy/` and `.claude/clancy/`
|
|
46
49
|
- Global: `~/.claude/commands/clancy/` and `~/.claude/clancy/`
|
|
47
50
|
|
|
48
|
-
Print:
|
|
51
|
+
Print: `✅ Clancy commands removed from [location].`
|
|
49
52
|
|
|
50
53
|
### Step 2b — Remove hooks
|
|
51
54
|
|
|
@@ -79,7 +82,7 @@ Read the full file content. Determine whether Clancy created the file or appende
|
|
|
79
82
|
- **Clancy created it** (the file contains only whitespace outside the markers — no meaningful content before `<!-- clancy:start -->` or after `<!-- clancy:end -->`): delete the entire file.
|
|
80
83
|
- **Clancy appended to an existing file** (there is meaningful content outside the markers): remove everything from `<!-- clancy:start -->` through `<!-- clancy:end -->` (inclusive), plus any blank lines immediately before the start marker that were added as spacing. Write the cleaned file back.
|
|
81
84
|
|
|
82
|
-
Print
|
|
85
|
+
Print `✅ CLAUDE.md cleaned up.` (or `✅ CLAUDE.md removed.` if deleted).
|
|
83
86
|
|
|
84
87
|
**If no markers found:** skip — Clancy didn't modify this file.
|
|
85
88
|
|
|
@@ -97,7 +100,7 @@ If it does, check whether it contains the Clancy entries (`# Clancy credentials`
|
|
|
97
100
|
|
|
98
101
|
If the file is now empty (or contains only whitespace) after removal, delete it entirely — Clancy created it during init.
|
|
99
102
|
|
|
100
|
-
Print
|
|
103
|
+
Print `✅ .gitignore cleaned up.` (or `✅ .gitignore removed.` if deleted).
|
|
101
104
|
|
|
102
105
|
**If not found:** skip — Clancy didn't modify this file.
|
|
103
106
|
|
|
@@ -116,8 +119,8 @@ If it does, ask separately:
|
|
|
116
119
|
Remove it too? This cannot be undone. (yes / no)
|
|
117
120
|
```
|
|
118
121
|
|
|
119
|
-
- `no` → print "
|
|
120
|
-
- `yes` → delete `.clancy/` and print "
|
|
122
|
+
- `no` → print "✅ .clancy/ kept — your docs and progress log are safe."
|
|
123
|
+
- `yes` → delete `.clancy/` and print "✅ .clancy/ removed."
|
|
121
124
|
|
|
122
125
|
If `.clancy/` does not exist, skip this step entirely.
|
|
123
126
|
|
|
@@ -126,7 +129,9 @@ If `.clancy/` does not exist, skip this step entirely.
|
|
|
126
129
|
## Step 6 — Final message
|
|
127
130
|
|
|
128
131
|
```
|
|
129
|
-
Clancy uninstalled.
|
|
132
|
+
✅ Clancy uninstalled.
|
|
133
|
+
|
|
134
|
+
"You have the right to remain silent... goodbye, Clancy." — To reinstall: npx chief-clancy
|
|
130
135
|
```
|
|
131
136
|
|
|
132
137
|
---
|
package/src/workflows/update.md
CHANGED
|
@@ -73,12 +73,13 @@ Compare installed vs latest:
|
|
|
73
73
|
|
|
74
74
|
**If installed == latest:**
|
|
75
75
|
```
|
|
76
|
-
|
|
76
|
+
🚨 Clancy — Update
|
|
77
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
77
78
|
|
|
78
79
|
**Installed:** X.Y.Z
|
|
79
80
|
**Latest:** X.Y.Z
|
|
80
81
|
|
|
81
|
-
You're already on the latest version.
|
|
82
|
+
✅ You're already on the latest version. "Nothing to see here, folks."
|
|
82
83
|
```
|
|
83
84
|
|
|
84
85
|
Exit.
|
|
@@ -92,7 +93,8 @@ curl -s https://raw.githubusercontent.com/Pushedskydiver/clancy/main/CHANGELOG.m
|
|
|
92
93
|
Extract only the entries between the installed version and the latest version. Display:
|
|
93
94
|
|
|
94
95
|
```
|
|
95
|
-
|
|
96
|
+
🚨 Clancy — Update
|
|
97
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
96
98
|
|
|
97
99
|
**Installed:** {installed}
|
|
98
100
|
**Latest:** {latest}
|
|
@@ -112,10 +114,10 @@ If you've modified any Clancy files directly, they'll be automatically backed up
|
|
|
112
114
|
to `.claude/clancy/local-patches/` before overwriting.
|
|
113
115
|
|
|
114
116
|
Your project files are preserved:
|
|
115
|
-
- `.clancy/` project folder (scripts, docs, .env, progress log)
|
|
116
|
-
- `CLAUDE.md`
|
|
117
|
-
- Custom commands not in `commands/clancy/`
|
|
118
|
-
- Custom hooks
|
|
117
|
+
- `.clancy/` project folder (scripts, docs, .env, progress log) ✅
|
|
118
|
+
- `CLAUDE.md` ✅
|
|
119
|
+
- Custom commands not in `commands/clancy/` ✅
|
|
120
|
+
- Custom hooks ✅
|
|
119
121
|
```
|
|
120
122
|
|
|
121
123
|
Ask the user: **"Proceed with update?"** with options:
|
|
@@ -137,13 +139,13 @@ npx -y chief-clancy@latest
|
|
|
137
139
|
The installer auto-detects whether to install globally or locally based on the existing install.
|
|
138
140
|
|
|
139
141
|
This only touches `.claude/commands/clancy/` and `.claude/clancy/workflows/`. It never modifies:
|
|
140
|
-
- `.clancy/clancy-once.
|
|
142
|
+
- `.clancy/clancy-once.js` or `.clancy/clancy-afk.js` — JS shims (these import from the installed chief-clancy package, so updating the package automatically updates the behavior)
|
|
141
143
|
- `.clancy/docs/` — codebase documentation
|
|
142
144
|
- `.clancy/progress.txt` — progress log
|
|
143
145
|
- `.clancy/.env` — credentials
|
|
144
146
|
- `CLAUDE.md`
|
|
145
147
|
|
|
146
|
-
**To
|
|
148
|
+
**To re-scaffold the JS shims**, re-run `/clancy:init` — it will detect the existing setup and re-create `.clancy/clancy-once.js` and `.clancy/clancy-afk.js` without asking for credentials again.
|
|
147
149
|
|
|
148
150
|
---
|
|
149
151
|
|
|
@@ -160,10 +162,10 @@ Display completion message:
|
|
|
160
162
|
|
|
161
163
|
```
|
|
162
164
|
╔═══════════════════════════════════════════════════════════╗
|
|
163
|
-
║ Clancy Updated: v{old} → v{new}
|
|
165
|
+
║ ✅ Clancy Updated: v{old} → v{new} ║
|
|
164
166
|
╚═══════════════════════════════════════════════════════════╝
|
|
165
167
|
|
|
166
|
-
|
|
168
|
+
"New badge, same Chief." — Restart Claude Code to pick up the new commands.
|
|
167
169
|
|
|
168
170
|
View full changelog: github.com/Pushedskydiver/clancy/blob/main/CHANGELOG.md
|
|
169
171
|
```
|
package/bin/install.js
DELETED
|
@@ -1,362 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
'use strict';
|
|
3
|
-
|
|
4
|
-
const fs = require('fs');
|
|
5
|
-
const path = require('path');
|
|
6
|
-
const readline = require('readline');
|
|
7
|
-
|
|
8
|
-
const PKG = require('../package.json');
|
|
9
|
-
const COMMANDS_SRC = path.join(__dirname, '..', 'src', 'commands');
|
|
10
|
-
const WORKFLOWS_SRC = path.join(__dirname, '..', 'src', 'workflows');
|
|
11
|
-
const HOOKS_SRC = path.join(__dirname, '..', 'hooks');
|
|
12
|
-
|
|
13
|
-
const homeDir = process.env.HOME || process.env.USERPROFILE;
|
|
14
|
-
if (!homeDir) {
|
|
15
|
-
process.stderr.write('\x1b[31m\n Error: HOME or USERPROFILE environment variable is not set.\x1b[0m\n');
|
|
16
|
-
process.exit(1);
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
const GLOBAL_DEST = path.join(homeDir, '.claude', 'commands', 'clancy');
|
|
20
|
-
const LOCAL_DEST = path.join(process.cwd(), '.claude', 'commands', 'clancy');
|
|
21
|
-
|
|
22
|
-
// Workflows live outside commands/ so Claude Code doesn't expose them as slash commands
|
|
23
|
-
const GLOBAL_WORKFLOWS_DEST = path.join(homeDir, '.claude', 'clancy', 'workflows');
|
|
24
|
-
const LOCAL_WORKFLOWS_DEST = path.join(process.cwd(), '.claude', 'clancy', 'workflows');
|
|
25
|
-
|
|
26
|
-
// ANSI helpers
|
|
27
|
-
const dim = s => `\x1b[2m${s}\x1b[0m`;
|
|
28
|
-
const bold = s => `\x1b[1m${s}\x1b[0m`;
|
|
29
|
-
const blue = s => `\x1b[1;34m${s}\x1b[0m`;
|
|
30
|
-
const cyan = s => `\x1b[36m${s}\x1b[0m`;
|
|
31
|
-
const green = s => `\x1b[32m${s}\x1b[0m`;
|
|
32
|
-
const red = s => `\x1b[31m${s}\x1b[0m`;
|
|
33
|
-
|
|
34
|
-
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
35
|
-
// Ensure readline is always closed — process.exit() doesn't trigger finally blocks
|
|
36
|
-
process.on('exit', () => rl.close());
|
|
37
|
-
|
|
38
|
-
function ask(label) {
|
|
39
|
-
return new Promise(resolve => rl.question(label, resolve));
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
async function choose(question, options, defaultChoice = 1) {
|
|
43
|
-
console.log('');
|
|
44
|
-
console.log(blue(question));
|
|
45
|
-
console.log('');
|
|
46
|
-
options.forEach((opt, i) => console.log(` ${i + 1}) ${opt}`));
|
|
47
|
-
console.log('');
|
|
48
|
-
const raw = await ask(cyan(`Choice [${defaultChoice}]: `));
|
|
49
|
-
return raw.trim() || String(defaultChoice);
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
const crypto = require('crypto');
|
|
53
|
-
|
|
54
|
-
function fileHash(filePath) {
|
|
55
|
-
const content = fs.readFileSync(filePath);
|
|
56
|
-
return crypto.createHash('sha256').digest('hex', content);
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
function copyDir(src, dest) {
|
|
60
|
-
// Use lstatSync (not statSync) to detect symlinks — statSync follows them and misreports
|
|
61
|
-
if (fs.existsSync(dest)) {
|
|
62
|
-
const stat = fs.lstatSync(dest);
|
|
63
|
-
if (stat.isSymbolicLink()) {
|
|
64
|
-
throw new Error(`${dest} is a symlink. Remove it first before installing.`);
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
fs.mkdirSync(dest, { recursive: true });
|
|
68
|
-
for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
|
|
69
|
-
const s = path.join(src, entry.name);
|
|
70
|
-
const d = path.join(dest, entry.name);
|
|
71
|
-
entry.isDirectory() ? copyDir(s, d) : fs.copyFileSync(s, d);
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
/**
|
|
76
|
-
* Build a manifest of installed files with SHA-256 hashes.
|
|
77
|
-
* Format: { "relative/path.md": "<sha256>", ... }
|
|
78
|
-
*/
|
|
79
|
-
function buildManifest(baseDir) {
|
|
80
|
-
const manifest = {};
|
|
81
|
-
function walk(dir, prefix) {
|
|
82
|
-
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
83
|
-
const full = path.join(dir, entry.name);
|
|
84
|
-
const rel = prefix ? `${prefix}/${entry.name}` : entry.name;
|
|
85
|
-
if (entry.isDirectory()) {
|
|
86
|
-
walk(full, rel);
|
|
87
|
-
} else {
|
|
88
|
-
const content = fs.readFileSync(full);
|
|
89
|
-
manifest[rel] = crypto.createHash('sha256').update(content).digest('hex');
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
walk(baseDir, '');
|
|
94
|
-
return manifest;
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
/**
|
|
98
|
-
* Detect files modified by the user since last install by comparing
|
|
99
|
-
* current file hashes against the stored manifest. Returns array of
|
|
100
|
-
* { rel, absPath } for modified files.
|
|
101
|
-
*/
|
|
102
|
-
function detectModifiedFiles(baseDir, manifestPath) {
|
|
103
|
-
if (!fs.existsSync(manifestPath)) return [];
|
|
104
|
-
let manifest;
|
|
105
|
-
try {
|
|
106
|
-
manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));
|
|
107
|
-
} catch { return []; }
|
|
108
|
-
|
|
109
|
-
const modified = [];
|
|
110
|
-
for (const [rel, hash] of Object.entries(manifest)) {
|
|
111
|
-
const absPath = path.join(baseDir, rel);
|
|
112
|
-
if (!fs.existsSync(absPath)) continue;
|
|
113
|
-
const content = fs.readFileSync(absPath);
|
|
114
|
-
const currentHash = crypto.createHash('sha256').update(content).digest('hex');
|
|
115
|
-
if (currentHash !== hash) {
|
|
116
|
-
modified.push({ rel, absPath });
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
return modified;
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
/**
|
|
123
|
-
* Back up modified files to a patches directory alongside the install.
|
|
124
|
-
* Returns the backup directory path if any files were backed up.
|
|
125
|
-
*/
|
|
126
|
-
function backupModifiedFiles(modified, patchesDir) {
|
|
127
|
-
if (modified.length === 0) return null;
|
|
128
|
-
fs.mkdirSync(patchesDir, { recursive: true });
|
|
129
|
-
for (const { rel, absPath } of modified) {
|
|
130
|
-
const backupPath = path.join(patchesDir, rel);
|
|
131
|
-
fs.mkdirSync(path.dirname(backupPath), { recursive: true });
|
|
132
|
-
fs.copyFileSync(absPath, backupPath);
|
|
133
|
-
}
|
|
134
|
-
// Write metadata so /clancy:update workflow knows what was backed up
|
|
135
|
-
fs.writeFileSync(
|
|
136
|
-
path.join(patchesDir, 'backup-meta.json'),
|
|
137
|
-
JSON.stringify({ backed_up: modified.map(m => m.rel), date: new Date().toISOString() }, null, 2)
|
|
138
|
-
);
|
|
139
|
-
return patchesDir;
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
async function main() {
|
|
143
|
-
console.log('');
|
|
144
|
-
console.log(blue(' ██████╗██╗ █████╗ ███╗ ██╗ ██████╗██╗ ██╗'));
|
|
145
|
-
console.log(blue(' ██╔════╝██║ ██╔══██╗████╗ ██║██╔════╝╚██╗ ██╔╝'));
|
|
146
|
-
console.log(blue(' ██║ ██║ ███████║██╔██╗ ██║██║ ╚████╔╝ '));
|
|
147
|
-
console.log(blue(' ██║ ██║ ██╔══██║██║╚██╗██║██║ ╚██╔╝ '));
|
|
148
|
-
console.log(blue(' ╚██████╗███████╗██║ ██║██║ ╚████║╚██████╗ ██║ '));
|
|
149
|
-
console.log(blue(' ╚═════╝╚══════╝╚═╝ ╚═╝╚═╝ ╚═══╝ ╚═════╝ ╚═╝ '));
|
|
150
|
-
console.log('');
|
|
151
|
-
console.log(' ' + bold(`v${PKG.version}`) + dim(' Autonomous, board-driven development for Claude Code.'));
|
|
152
|
-
console.log(dim(' Named after Chief Clancy Wiggum. Built on the Ralph technique by Geoffrey Huntley.'));
|
|
153
|
-
|
|
154
|
-
const installChoice = await choose(
|
|
155
|
-
'Where would you like to install?',
|
|
156
|
-
[
|
|
157
|
-
`Global ${dim('(~/.claude)')} — available in all projects`,
|
|
158
|
-
`Local ${dim('(./.claude)')} — this project only`,
|
|
159
|
-
]
|
|
160
|
-
);
|
|
161
|
-
|
|
162
|
-
let dest, workflowsDest;
|
|
163
|
-
if (installChoice === '1' || installChoice.toLowerCase() === 'global') {
|
|
164
|
-
dest = GLOBAL_DEST;
|
|
165
|
-
workflowsDest = GLOBAL_WORKFLOWS_DEST;
|
|
166
|
-
} else if (installChoice === '2' || installChoice.toLowerCase() === 'local') {
|
|
167
|
-
dest = LOCAL_DEST;
|
|
168
|
-
workflowsDest = LOCAL_WORKFLOWS_DEST;
|
|
169
|
-
} else {
|
|
170
|
-
console.log(red('\n Invalid choice. Run npx chief-clancy again and enter 1 or 2.'));
|
|
171
|
-
rl.close();
|
|
172
|
-
process.exit(1);
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
// Validate source directories — guards against corrupted npm package
|
|
176
|
-
if (!fs.existsSync(COMMANDS_SRC)) {
|
|
177
|
-
console.error(red(`\n Error: Source not found: ${COMMANDS_SRC}`));
|
|
178
|
-
console.error(red(' The npm package may be corrupted. Try: npm cache clean --force'));
|
|
179
|
-
rl.close();
|
|
180
|
-
process.exit(1);
|
|
181
|
-
}
|
|
182
|
-
if (!fs.existsSync(WORKFLOWS_SRC)) {
|
|
183
|
-
console.error(red(`\n Error: Source not found: ${WORKFLOWS_SRC}`));
|
|
184
|
-
console.error(red(' The npm package may be corrupted. Try: npm cache clean --force'));
|
|
185
|
-
rl.close();
|
|
186
|
-
process.exit(1);
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
console.log('');
|
|
190
|
-
console.log(dim(` Installing to: ${dest}`));
|
|
191
|
-
|
|
192
|
-
try {
|
|
193
|
-
// Determine manifest and patches paths (sibling to commands dir)
|
|
194
|
-
const claudeDir = path.dirname(path.dirname(dest)); // .claude/ (parent of commands/)
|
|
195
|
-
const manifestPath = path.join(claudeDir, 'clancy', 'manifest.json');
|
|
196
|
-
const patchesDir = path.join(claudeDir, 'clancy', 'local-patches');
|
|
197
|
-
|
|
198
|
-
if (fs.existsSync(dest) || fs.existsSync(workflowsDest)) {
|
|
199
|
-
console.log('');
|
|
200
|
-
|
|
201
|
-
// Detect user-modified files before overwriting
|
|
202
|
-
const modified = detectModifiedFiles(dest, manifestPath);
|
|
203
|
-
const modifiedWorkflows = detectModifiedFiles(workflowsDest, manifestPath.replace('manifest.json', 'workflows-manifest.json'));
|
|
204
|
-
const allModified = [...modified, ...modifiedWorkflows];
|
|
205
|
-
|
|
206
|
-
if (allModified.length > 0) {
|
|
207
|
-
console.log(blue(' Modified files detected:'));
|
|
208
|
-
for (const { rel } of allModified) {
|
|
209
|
-
console.log(` ${dim('•')} ${rel}`);
|
|
210
|
-
}
|
|
211
|
-
console.log('');
|
|
212
|
-
console.log(dim(' These will be backed up to .claude/clancy/local-patches/'));
|
|
213
|
-
console.log(dim(' before overwriting. You can reapply them after the update.'));
|
|
214
|
-
console.log('');
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
const overwrite = await ask(blue(` Commands already exist at ${dest}. Overwrite? [y/N] `));
|
|
218
|
-
if (!overwrite.trim().toLowerCase().startsWith('y')) {
|
|
219
|
-
console.log('\n Aborted. No files changed.');
|
|
220
|
-
rl.close();
|
|
221
|
-
process.exit(0);
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
// Back up modified files before overwriting
|
|
225
|
-
if (allModified.length > 0) {
|
|
226
|
-
backupModifiedFiles(allModified, patchesDir);
|
|
227
|
-
console.log(green(`\n ✓ ${allModified.length} modified file(s) backed up to local-patches/`));
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
copyDir(COMMANDS_SRC, dest);
|
|
232
|
-
copyDir(WORKFLOWS_SRC, workflowsDest);
|
|
233
|
-
|
|
234
|
-
// For global installs, @-file references in command files resolve relative to the
|
|
235
|
-
// project root — not ~/.claude/ — so the workflow files won't be found at runtime.
|
|
236
|
-
// Fix: inline the workflow content directly into the installed command files.
|
|
237
|
-
if (dest === GLOBAL_DEST) {
|
|
238
|
-
const WORKFLOW_REF = /^@\.claude\/clancy\/workflows\/(.+\.md)$/m;
|
|
239
|
-
for (const file of fs.readdirSync(dest)) {
|
|
240
|
-
if (!file.endsWith('.md')) continue;
|
|
241
|
-
const cmdPath = path.join(dest, file);
|
|
242
|
-
const content = fs.readFileSync(cmdPath, 'utf8');
|
|
243
|
-
const match = content.match(WORKFLOW_REF);
|
|
244
|
-
if (!match) continue;
|
|
245
|
-
const workflowFile = path.join(workflowsDest, match[1]);
|
|
246
|
-
if (!fs.existsSync(workflowFile)) continue;
|
|
247
|
-
const workflowContent = fs.readFileSync(workflowFile, 'utf8');
|
|
248
|
-
fs.writeFileSync(cmdPath, content.replace(match[0], workflowContent));
|
|
249
|
-
}
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
// Write VERSION file so /clancy:doctor and /clancy:update can read the installed version
|
|
253
|
-
fs.writeFileSync(path.join(dest, 'VERSION'), PKG.version);
|
|
254
|
-
|
|
255
|
-
// Write manifests so future updates can detect user-modified files
|
|
256
|
-
fs.mkdirSync(path.dirname(manifestPath), { recursive: true });
|
|
257
|
-
fs.writeFileSync(manifestPath, JSON.stringify(buildManifest(dest), null, 2));
|
|
258
|
-
fs.writeFileSync(
|
|
259
|
-
manifestPath.replace('manifest.json', 'workflows-manifest.json'),
|
|
260
|
-
JSON.stringify(buildManifest(workflowsDest), null, 2)
|
|
261
|
-
);
|
|
262
|
-
|
|
263
|
-
// Install hooks and register them in Claude settings.json
|
|
264
|
-
const claudeConfigDir = dest === GLOBAL_DEST
|
|
265
|
-
? path.join(homeDir, '.claude')
|
|
266
|
-
: path.join(process.cwd(), '.claude');
|
|
267
|
-
const hooksInstallDir = path.join(claudeConfigDir, 'hooks');
|
|
268
|
-
const settingsFile = path.join(claudeConfigDir, 'settings.json');
|
|
269
|
-
|
|
270
|
-
const hookFiles = [
|
|
271
|
-
'clancy-check-update.js',
|
|
272
|
-
'clancy-statusline.js',
|
|
273
|
-
'clancy-context-monitor.js',
|
|
274
|
-
'clancy-credential-guard.js',
|
|
275
|
-
];
|
|
276
|
-
|
|
277
|
-
try {
|
|
278
|
-
fs.mkdirSync(hooksInstallDir, { recursive: true });
|
|
279
|
-
for (const f of hookFiles) {
|
|
280
|
-
fs.copyFileSync(path.join(HOOKS_SRC, f), path.join(hooksInstallDir, f));
|
|
281
|
-
}
|
|
282
|
-
// Force CommonJS resolution for hook files — projects with "type":"module"
|
|
283
|
-
// in their package.json would otherwise treat .js files as ESM, breaking require().
|
|
284
|
-
fs.writeFileSync(
|
|
285
|
-
path.join(hooksInstallDir, 'package.json'),
|
|
286
|
-
JSON.stringify({ type: 'commonjs' }, null, 2) + '\n'
|
|
287
|
-
);
|
|
288
|
-
|
|
289
|
-
// Merge hooks into settings.json without clobbering existing config
|
|
290
|
-
let settings = {};
|
|
291
|
-
if (fs.existsSync(settingsFile)) {
|
|
292
|
-
try { settings = JSON.parse(fs.readFileSync(settingsFile, 'utf8')); } catch {}
|
|
293
|
-
}
|
|
294
|
-
if (!settings.hooks) settings.hooks = {};
|
|
295
|
-
|
|
296
|
-
// Helper: add a hook command to an event array if not already present
|
|
297
|
-
function registerHook(event, command) {
|
|
298
|
-
if (!settings.hooks[event]) settings.hooks[event] = [];
|
|
299
|
-
const already = settings.hooks[event].some(
|
|
300
|
-
h => h.hooks && h.hooks.some(hh => hh.command === command)
|
|
301
|
-
);
|
|
302
|
-
if (!already) {
|
|
303
|
-
settings.hooks[event].push({ hooks: [{ type: 'command', command }] });
|
|
304
|
-
}
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
const updateScript = path.join(hooksInstallDir, 'clancy-check-update.js');
|
|
308
|
-
const statuslineScript = path.join(hooksInstallDir, 'clancy-statusline.js');
|
|
309
|
-
const monitorScript = path.join(hooksInstallDir, 'clancy-context-monitor.js');
|
|
310
|
-
const guardScript = path.join(hooksInstallDir, 'clancy-credential-guard.js');
|
|
311
|
-
|
|
312
|
-
registerHook('SessionStart', `node ${updateScript}`);
|
|
313
|
-
registerHook('PostToolUse', `node ${monitorScript}`);
|
|
314
|
-
registerHook('PreToolUse', `node ${guardScript}`);
|
|
315
|
-
|
|
316
|
-
// Statusline: registered as top-level key, not inside hooks
|
|
317
|
-
if (!settings.statusLine) {
|
|
318
|
-
settings.statusLine = { type: 'command', command: `node ${statuslineScript}` };
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
fs.writeFileSync(settingsFile, JSON.stringify(settings, null, 2) + '\n');
|
|
322
|
-
} catch {
|
|
323
|
-
// Hook registration is best-effort — don't fail the install over it
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
console.log('');
|
|
327
|
-
console.log(green(' ✓ Clancy installed successfully.'));
|
|
328
|
-
console.log('');
|
|
329
|
-
console.log(' Next steps:');
|
|
330
|
-
console.log(dim(' 1. Open a project in Claude Code'));
|
|
331
|
-
console.log(` 2. Run: ${cyan('/clancy:init')}`);
|
|
332
|
-
console.log('');
|
|
333
|
-
console.log(' Commands available:');
|
|
334
|
-
console.log('');
|
|
335
|
-
const cmds = [
|
|
336
|
-
['/clancy:init', 'Set up Clancy in your project'],
|
|
337
|
-
['/clancy:map-codebase', 'Scan codebase with 5 parallel agents'],
|
|
338
|
-
['/clancy:run', 'Run Clancy in loop mode'],
|
|
339
|
-
['/clancy:once', 'Pick up one ticket and stop'],
|
|
340
|
-
['/clancy:dry-run', 'Preview next ticket without making changes'],
|
|
341
|
-
['/clancy:status', 'Show next tickets without running'],
|
|
342
|
-
['/clancy:review', 'Score next ticket and get recommendations'],
|
|
343
|
-
['/clancy:logs', 'Display progress log'],
|
|
344
|
-
['/clancy:settings', 'View and change configuration'],
|
|
345
|
-
['/clancy:doctor', 'Diagnose your setup'],
|
|
346
|
-
['/clancy:update', 'Update Clancy to latest version'],
|
|
347
|
-
['/clancy:help', 'Show all commands'],
|
|
348
|
-
];
|
|
349
|
-
for (const [cmd, desc] of cmds) {
|
|
350
|
-
console.log(` ${cyan(cmd.padEnd(22))} ${dim(desc)}`);
|
|
351
|
-
}
|
|
352
|
-
console.log('');
|
|
353
|
-
} catch (err) {
|
|
354
|
-
console.error(red(`\n Install failed: ${err.message}`));
|
|
355
|
-
rl.close();
|
|
356
|
-
process.exit(1);
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
rl.close();
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
main();
|