chief-clancy 0.1.7 → 0.2.0-beta.2

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 CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  **Autonomous, board-driven development for Claude Code.**
4
4
 
5
- [![npm](https://img.shields.io/npm/v/chief-clancy?color=cb3837)](https://www.npmjs.com/package/chief-clancy) [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](./LICENSE) [![Tests](https://img.shields.io/badge/tests-51%20passing-brightgreen)](./test/) [![GitHub Stars](https://img.shields.io/github/stars/Pushedskydiver/clancy?style=flat)](https://github.com/Pushedskydiver/clancy/stargazers)
5
+ [![npm](https://img.shields.io/npm/v/chief-clancy?color=cb3837)](https://www.npmjs.com/package/chief-clancy) [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](./LICENSE) [![Tests](https://img.shields.io/badge/tests-55%20passing-brightgreen)](./test/) [![GitHub Stars](https://img.shields.io/github/stars/Pushedskydiver/clancy?style=flat)](https://github.com/Pushedskydiver/clancy/stargazers)
6
6
 
7
7
  ```bash
8
8
  npx chief-clancy
@@ -130,10 +130,13 @@ npx chief-clancy
130
130
  # 3. Scan your codebase (or say yes during init)
131
131
  /clancy:map-codebase
132
132
 
133
- # 4. Watch your first ticket
133
+ # 4. Preview the first ticket (no changes made)
134
+ /clancy:dry-run
135
+
136
+ # 5. Watch your first ticket
134
137
  /clancy:once
135
138
 
136
- # 5. Go AFK
139
+ # 6. Go AFK
137
140
  /clancy:run
138
141
  ```
139
142
 
@@ -147,6 +150,7 @@ npx chief-clancy
147
150
  | `/clancy:run` | Loop mode — processes tickets until queue is empty or MAX_ITERATIONS hit |
148
151
  | `/clancy:run 20` | Same, override MAX_ITERATIONS to 20 for this session |
149
152
  | `/clancy:once` | Pick up one ticket and stop |
153
+ | `/clancy:dry-run` | Preview next ticket without making changes — no git ops, no Claude call |
150
154
  | `/clancy:status` | Show next tickets without running — read-only |
151
155
  | `/clancy:review` | Score next ticket (0–100%) with actionable recommendations |
152
156
  | `/clancy:logs` | Format and display `.clancy/progress.txt` |
@@ -188,7 +192,7 @@ Clancy also merges a section into your `CLAUDE.md` (or creates one) that tells C
188
192
 
189
193
  ## Optional enhancements
190
194
 
191
- Set during `/clancy:init` advanced setup, or by editing `.clancy/.env` directly. Use `/clancy:settings` → "Save as defaults" to save non-credential settings to `~/.clancy/defaults.json` — new projects created with `/clancy:init` will inherit them automatically.
195
+ Set during `/clancy:init` advanced setup, or by editing `.clancy/.env` directly.
192
196
 
193
197
  ### Figma MCP
194
198
 
@@ -215,6 +219,15 @@ PLAYWRIGHT_STARTUP_WAIT=15
215
219
 
216
220
  After implementing a UI ticket, Clancy starts the dev server or Storybook, screenshots, assesses visually, checks the console, and fixes anything wrong before committing.
217
221
 
222
+ ### Status transitions
223
+
224
+ ```
225
+ CLANCY_STATUS_IN_PROGRESS="In Progress"
226
+ CLANCY_STATUS_DONE="Done"
227
+ ```
228
+
229
+ Clancy automatically moves tickets through your board when it picks up and completes them. Set these to the exact column name shown in your Jira or Linear board. Best-effort — a failed transition never stops the run. Configurable via `/clancy:settings`.
230
+
218
231
  ### Notifications
219
232
 
220
233
  ```
@@ -405,8 +418,6 @@ lsof -ti:5173 | xargs kill -9 # replace 5173 with your PLAYWRIGHT_DEV_PORT
405
418
 
406
419
  Or directly: `npx chief-clancy@latest`
407
420
 
408
- The update workflow shows what's changed (changelog diff) and asks for confirmation before overwriting. If you've customised any command or workflow files, they're automatically backed up to `.claude/clancy/local-patches/` before the update — check there to reapply your changes afterwards.
409
-
410
421
  ---
411
422
 
412
423
  **Uninstalling?**
@@ -415,7 +426,7 @@ The update workflow shows what's changed (changelog diff) and asks for confirmat
415
426
  /clancy:uninstall
416
427
  ```
417
428
 
418
- Removes slash commands from your chosen location. Cleans up the `<!-- clancy:start -->` / `<!-- clancy:end -->` block from `CLAUDE.md` (or deletes it entirely if Clancy created it) and removes the `.clancy/.env` entry from `.gitignore`. Optionally removes `.clancy/` (credentials and docs).
429
+ Removes slash commands from your chosen location. Optionally removes `.clancy/` (credentials and docs). Never touches `CLAUDE.md`.
419
430
 
420
431
  ---
421
432
 
package/bin/install.js CHANGED
@@ -8,6 +8,7 @@ const readline = require('readline');
8
8
  const PKG = require('../package.json');
9
9
  const COMMANDS_SRC = path.join(__dirname, '..', 'src', 'commands');
10
10
  const WORKFLOWS_SRC = path.join(__dirname, '..', 'src', 'workflows');
11
+ const HOOKS_SRC = path.join(__dirname, '..', 'hooks');
11
12
 
12
13
  const homeDir = process.env.HOME || process.env.USERPROFILE;
13
14
  if (!homeDir) {
@@ -48,13 +49,6 @@ async function choose(question, options, defaultChoice = 1) {
48
49
  return raw.trim() || String(defaultChoice);
49
50
  }
50
51
 
51
- const crypto = require('crypto');
52
-
53
- function fileHash(filePath) {
54
- const content = fs.readFileSync(filePath);
55
- return crypto.createHash('sha256').digest('hex', content);
56
- }
57
-
58
52
  function copyDir(src, dest) {
59
53
  // Use lstatSync (not statSync) to detect symlinks — statSync follows them and misreports
60
54
  if (fs.existsSync(dest)) {
@@ -71,73 +65,6 @@ function copyDir(src, dest) {
71
65
  }
72
66
  }
73
67
 
74
- /**
75
- * Build a manifest of installed files with SHA-256 hashes.
76
- * Format: { "relative/path.md": "<sha256>", ... }
77
- */
78
- function buildManifest(baseDir) {
79
- const manifest = {};
80
- function walk(dir, prefix) {
81
- for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
82
- const full = path.join(dir, entry.name);
83
- const rel = prefix ? `${prefix}/${entry.name}` : entry.name;
84
- if (entry.isDirectory()) {
85
- walk(full, rel);
86
- } else {
87
- const content = fs.readFileSync(full);
88
- manifest[rel] = crypto.createHash('sha256').update(content).digest('hex');
89
- }
90
- }
91
- }
92
- walk(baseDir, '');
93
- return manifest;
94
- }
95
-
96
- /**
97
- * Detect files modified by the user since last install by comparing
98
- * current file hashes against the stored manifest. Returns array of
99
- * { rel, absPath } for modified files.
100
- */
101
- function detectModifiedFiles(baseDir, manifestPath) {
102
- if (!fs.existsSync(manifestPath)) return [];
103
- let manifest;
104
- try {
105
- manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));
106
- } catch { return []; }
107
-
108
- const modified = [];
109
- for (const [rel, hash] of Object.entries(manifest)) {
110
- const absPath = path.join(baseDir, rel);
111
- if (!fs.existsSync(absPath)) continue;
112
- const content = fs.readFileSync(absPath);
113
- const currentHash = crypto.createHash('sha256').update(content).digest('hex');
114
- if (currentHash !== hash) {
115
- modified.push({ rel, absPath });
116
- }
117
- }
118
- return modified;
119
- }
120
-
121
- /**
122
- * Back up modified files to a patches directory alongside the install.
123
- * Returns the backup directory path if any files were backed up.
124
- */
125
- function backupModifiedFiles(modified, patchesDir) {
126
- if (modified.length === 0) return null;
127
- fs.mkdirSync(patchesDir, { recursive: true });
128
- for (const { rel, absPath } of modified) {
129
- const backupPath = path.join(patchesDir, rel);
130
- fs.mkdirSync(path.dirname(backupPath), { recursive: true });
131
- fs.copyFileSync(absPath, backupPath);
132
- }
133
- // Write metadata so /clancy:update workflow knows what was backed up
134
- fs.writeFileSync(
135
- path.join(patchesDir, 'backup-meta.json'),
136
- JSON.stringify({ backed_up: modified.map(m => m.rel), date: new Date().toISOString() }, null, 2)
137
- );
138
- return patchesDir;
139
- }
140
-
141
68
  async function main() {
142
69
  console.log('');
143
70
  console.log(blue(' ██████╗██╗ █████╗ ███╗ ██╗ ██████╗██╗ ██╗'));
@@ -189,42 +116,14 @@ async function main() {
189
116
  console.log(dim(` Installing to: ${dest}`));
190
117
 
191
118
  try {
192
- // Determine manifest and patches paths (sibling to commands dir)
193
- const claudeDir = path.dirname(path.dirname(dest)); // .claude/ (parent of commands/)
194
- const manifestPath = path.join(claudeDir, 'clancy', 'manifest.json');
195
- const patchesDir = path.join(claudeDir, 'clancy', 'local-patches');
196
-
197
119
  if (fs.existsSync(dest) || fs.existsSync(workflowsDest)) {
198
120
  console.log('');
199
-
200
- // Detect user-modified files before overwriting
201
- const modified = detectModifiedFiles(dest, manifestPath);
202
- const modifiedWorkflows = detectModifiedFiles(workflowsDest, manifestPath.replace('manifest.json', 'workflows-manifest.json'));
203
- const allModified = [...modified, ...modifiedWorkflows];
204
-
205
- if (allModified.length > 0) {
206
- console.log(blue(' Modified files detected:'));
207
- for (const { rel } of allModified) {
208
- console.log(` ${dim('•')} ${rel}`);
209
- }
210
- console.log('');
211
- console.log(dim(' These will be backed up to .claude/clancy/local-patches/'));
212
- console.log(dim(' before overwriting. You can reapply them after the update.'));
213
- console.log('');
214
- }
215
-
216
121
  const overwrite = await ask(blue(` Commands already exist at ${dest}. Overwrite? [y/N] `));
217
122
  if (!overwrite.trim().toLowerCase().startsWith('y')) {
218
123
  console.log('\n Aborted. No files changed.');
219
124
  rl.close();
220
125
  process.exit(0);
221
126
  }
222
-
223
- // Back up modified files before overwriting
224
- if (allModified.length > 0) {
225
- backupModifiedFiles(allModified, patchesDir);
226
- console.log(green(`\n ✓ ${allModified.length} modified file(s) backed up to local-patches/`));
227
- }
228
127
  }
229
128
 
230
129
  copyDir(COMMANDS_SRC, dest);
@@ -251,13 +150,65 @@ async function main() {
251
150
  // Write VERSION file so /clancy:doctor and /clancy:update can read the installed version
252
151
  fs.writeFileSync(path.join(dest, 'VERSION'), PKG.version);
253
152
 
254
- // Write manifests so future updates can detect user-modified files
255
- fs.mkdirSync(path.dirname(manifestPath), { recursive: true });
256
- fs.writeFileSync(manifestPath, JSON.stringify(buildManifest(dest), null, 2));
257
- fs.writeFileSync(
258
- manifestPath.replace('manifest.json', 'workflows-manifest.json'),
259
- JSON.stringify(buildManifest(workflowsDest), null, 2)
260
- );
153
+ // Install hooks and register them in Claude settings.json
154
+ const claudeConfigDir = dest === GLOBAL_DEST
155
+ ? path.join(homeDir, '.claude')
156
+ : path.join(process.cwd(), '.claude');
157
+ const hooksInstallDir = path.join(claudeConfigDir, 'hooks');
158
+ const settingsFile = path.join(claudeConfigDir, 'settings.json');
159
+
160
+ const hookFiles = [
161
+ 'clancy-check-update.js',
162
+ 'clancy-statusline.js',
163
+ 'clancy-context-monitor.js',
164
+ ];
165
+
166
+ try {
167
+ fs.mkdirSync(hooksInstallDir, { recursive: true });
168
+ for (const f of hookFiles) {
169
+ fs.copyFileSync(path.join(HOOKS_SRC, f), path.join(hooksInstallDir, f));
170
+ }
171
+ // Force CommonJS resolution for hook files — projects with "type":"module"
172
+ // in their package.json would otherwise treat .js files as ESM, breaking require().
173
+ fs.writeFileSync(
174
+ path.join(hooksInstallDir, 'package.json'),
175
+ JSON.stringify({ type: 'commonjs' }, null, 2) + '\n'
176
+ );
177
+
178
+ // Merge hooks into settings.json without clobbering existing config
179
+ let settings = {};
180
+ if (fs.existsSync(settingsFile)) {
181
+ try { settings = JSON.parse(fs.readFileSync(settingsFile, 'utf8')); } catch {}
182
+ }
183
+ if (!settings.hooks) settings.hooks = {};
184
+
185
+ // Helper: add a hook command to an event array if not already present
186
+ function registerHook(event, command) {
187
+ if (!settings.hooks[event]) settings.hooks[event] = [];
188
+ const already = settings.hooks[event].some(
189
+ h => h.hooks && h.hooks.some(hh => hh.command === command)
190
+ );
191
+ if (!already) {
192
+ settings.hooks[event].push({ hooks: [{ type: 'command', command }] });
193
+ }
194
+ }
195
+
196
+ const updateScript = path.join(hooksInstallDir, 'clancy-check-update.js');
197
+ const statuslineScript = path.join(hooksInstallDir, 'clancy-statusline.js');
198
+ const monitorScript = path.join(hooksInstallDir, 'clancy-context-monitor.js');
199
+
200
+ registerHook('SessionStart', `node ${updateScript}`);
201
+ registerHook('PostToolUse', `node ${monitorScript}`);
202
+
203
+ // Statusline: registered as top-level key, not inside hooks
204
+ if (!settings.statusLine) {
205
+ settings.statusLine = { type: 'command', command: `node ${statuslineScript}` };
206
+ }
207
+
208
+ fs.writeFileSync(settingsFile, JSON.stringify(settings, null, 2) + '\n');
209
+ } catch {
210
+ // Hook registration is best-effort — don't fail the install over it
211
+ }
261
212
 
262
213
  console.log('');
263
214
  console.log(green(' ✓ Clancy installed successfully.'));
@@ -273,6 +224,7 @@ async function main() {
273
224
  ['/clancy:map-codebase', 'Scan codebase with 5 parallel agents'],
274
225
  ['/clancy:run', 'Run Clancy in loop mode'],
275
226
  ['/clancy:once', 'Pick up one ticket and stop'],
227
+ ['/clancy:dry-run', 'Preview next ticket without making changes'],
276
228
  ['/clancy:status', 'Show next tickets without running'],
277
229
  ['/clancy:review', 'Score next ticket and get recommendations'],
278
230
  ['/clancy:logs', 'Display progress log'],
@@ -0,0 +1,71 @@
1
+ #!/usr/bin/env node
2
+ // SessionStart hook: check for Clancy updates in the background.
3
+ // Spawns a detached child process to hit npm, writes result to cache.
4
+ // Claude reads the cache via CLAUDE.md instruction — no output from this script.
5
+
6
+ 'use strict';
7
+
8
+ const fs = require('fs');
9
+ const path = require('path');
10
+ const os = require('os');
11
+ const { spawn } = require('child_process');
12
+
13
+ const homeDir = os.homedir();
14
+ const cwd = process.cwd();
15
+
16
+ // Resolve the Clancy install dir (local takes priority over global).
17
+ function findInstallDir() {
18
+ const localVersion = path.join(cwd, '.claude', 'commands', 'clancy', 'VERSION');
19
+ const globalVersion = path.join(homeDir, '.claude', 'commands', 'clancy', 'VERSION');
20
+ if (fs.existsSync(localVersion)) return path.dirname(localVersion);
21
+ if (fs.existsSync(globalVersion)) return path.dirname(globalVersion);
22
+ return null;
23
+ }
24
+
25
+ const installDir = findInstallDir();
26
+ if (!installDir) process.exit(0); // Clancy not installed — nothing to check
27
+
28
+ const cacheDir = path.join(homeDir, '.claude', 'cache');
29
+ const cacheFile = path.join(cacheDir, 'clancy-update-check.json');
30
+ const versionFile = path.join(installDir, 'VERSION');
31
+
32
+ if (!fs.existsSync(cacheDir)) {
33
+ try { fs.mkdirSync(cacheDir, { recursive: true }); } catch { process.exit(0); }
34
+ }
35
+
36
+ // Spawn a detached background process to do the actual npm check.
37
+ // This script returns immediately so it never delays session start.
38
+ const child = spawn(process.execPath, ['-e', `
39
+ const fs = require('fs');
40
+ const { execSync } = require('child_process');
41
+
42
+ const cacheFile = ${JSON.stringify(cacheFile)};
43
+ const versionFile = ${JSON.stringify(versionFile)};
44
+
45
+ let installed = '0.0.0';
46
+ try { installed = fs.readFileSync(versionFile, 'utf8').trim(); } catch {}
47
+
48
+ let latest = null;
49
+ try {
50
+ latest = execSync('npm view chief-clancy version', {
51
+ encoding: 'utf8',
52
+ timeout: 10000,
53
+ windowsHide: true,
54
+ }).trim();
55
+ } catch {}
56
+
57
+ const result = {
58
+ update_available: Boolean(latest && installed !== latest),
59
+ installed,
60
+ latest: latest || 'unknown',
61
+ checked: Math.floor(Date.now() / 1000),
62
+ };
63
+
64
+ try { fs.writeFileSync(cacheFile, JSON.stringify(result)); } catch {}
65
+ `], {
66
+ stdio: 'ignore',
67
+ windowsHide: true,
68
+ detached: true,
69
+ });
70
+
71
+ child.unref();
@@ -0,0 +1,101 @@
1
+ #!/usr/bin/env node
2
+ // Clancy Context Monitor — PostToolUse hook.
3
+ // Reads context metrics from the bridge file written by clancy-statusline.js
4
+ // and injects warnings into Claude's conversation when context runs low.
5
+ //
6
+ // Thresholds:
7
+ // WARNING (remaining <= 35%): wrap up analysis, move to implementation
8
+ // CRITICAL (remaining <= 25%): commit current work, log to .clancy/progress.txt, stop
9
+ //
10
+ // Debounce: 5 tool uses between warnings; severity escalation bypasses debounce.
11
+
12
+ 'use strict';
13
+
14
+ const fs = require('fs');
15
+ const os = require('os');
16
+ const path = require('path');
17
+
18
+ const WARNING_THRESHOLD = 35;
19
+ const CRITICAL_THRESHOLD = 25;
20
+ const STALE_SECONDS = 60;
21
+ const DEBOUNCE_CALLS = 5;
22
+
23
+ let input = '';
24
+ const stdinTimeout = setTimeout(() => process.exit(0), 3000);
25
+ process.stdin.setEncoding('utf8');
26
+ process.stdin.on('data', chunk => { input += chunk; });
27
+ process.stdin.on('end', () => {
28
+ clearTimeout(stdinTimeout);
29
+ try {
30
+ const data = JSON.parse(input);
31
+ const session = data.session_id;
32
+ if (!session) process.exit(0);
33
+
34
+ const bridgePath = path.join(os.tmpdir(), `clancy-ctx-${session}.json`);
35
+ if (!fs.existsSync(bridgePath)) process.exit(0); // no statusline data yet
36
+
37
+ const metrics = JSON.parse(fs.readFileSync(bridgePath, 'utf8'));
38
+ const now = Math.floor(Date.now() / 1000);
39
+
40
+ // Ignore stale metrics (statusline may not have run this session)
41
+ if (metrics.timestamp && (now - metrics.timestamp) > STALE_SECONDS) process.exit(0);
42
+
43
+ const remaining = metrics.remaining_percentage;
44
+ const usedPct = metrics.used_pct;
45
+
46
+ if (remaining > WARNING_THRESHOLD) process.exit(0);
47
+
48
+ // Debounce
49
+ const warnPath = path.join(os.tmpdir(), `clancy-ctx-${session}-warned.json`);
50
+ let warnData = { callsSinceWarn: 0, lastLevel: null };
51
+ let firstWarn = true;
52
+
53
+ if (fs.existsSync(warnPath)) {
54
+ try { warnData = JSON.parse(fs.readFileSync(warnPath, 'utf8')); firstWarn = false; } catch {}
55
+ }
56
+
57
+ warnData.callsSinceWarn = (warnData.callsSinceWarn || 0) + 1;
58
+
59
+ const isCritical = remaining <= CRITICAL_THRESHOLD;
60
+ const currentLevel = isCritical ? 'critical' : 'warning';
61
+ const severityEscalated = currentLevel === 'critical' && warnData.lastLevel === 'warning';
62
+
63
+ if (!firstWarn && warnData.callsSinceWarn < DEBOUNCE_CALLS && !severityEscalated) {
64
+ fs.writeFileSync(warnPath, JSON.stringify(warnData));
65
+ process.exit(0);
66
+ }
67
+
68
+ warnData.callsSinceWarn = 0;
69
+ warnData.lastLevel = currentLevel;
70
+ fs.writeFileSync(warnPath, JSON.stringify(warnData));
71
+
72
+ let message;
73
+ if (isCritical) {
74
+ message =
75
+ `CONTEXT CRITICAL: Usage at ${usedPct}%. Remaining: ${remaining}%. ` +
76
+ 'Context is nearly exhausted. Stop reading files and wrap up immediately:\n' +
77
+ '1. Commit whatever work is staged on the current feature branch\n' +
78
+ '2. Append a WIP entry to .clancy/progress.txt: ' +
79
+ 'YYYY-MM-DD HH:MM | TICKET-KEY | Summary | WIP — context exhausted\n' +
80
+ '3. Inform the user what was completed and what remains.\n' +
81
+ 'Do NOT start any new work.';
82
+ } else {
83
+ message =
84
+ `CONTEXT WARNING: Usage at ${usedPct}%. Remaining: ${remaining}%. ` +
85
+ 'Context is getting limited. Stop exploring and move to implementation. ' +
86
+ 'Avoid reading additional files unless strictly necessary. ' +
87
+ 'Commit completed work as soon as it is ready.';
88
+ }
89
+
90
+ const output = {
91
+ hookSpecificOutput: {
92
+ hookEventName: 'PostToolUse',
93
+ additionalContext: message,
94
+ },
95
+ };
96
+
97
+ process.stdout.write(JSON.stringify(output));
98
+ } catch {
99
+ process.exit(0);
100
+ }
101
+ });
@@ -0,0 +1,82 @@
1
+ #!/usr/bin/env node
2
+ // Clancy Statusline hook — registered as the Claude Code statusline.
3
+ // Two jobs:
4
+ // 1. Write context metrics to a bridge file so the PostToolUse context
5
+ // monitor can read them (the statusline is the only hook that receives
6
+ // context_window data directly).
7
+ // 2. Output a statusline string showing context usage and update status.
8
+
9
+ 'use strict';
10
+
11
+ const fs = require('fs');
12
+ const path = require('path');
13
+ const os = require('os');
14
+
15
+ const homeDir = os.homedir();
16
+
17
+ let input = '';
18
+ const stdinTimeout = setTimeout(() => process.exit(0), 3000);
19
+ process.stdin.setEncoding('utf8');
20
+ process.stdin.on('data', chunk => { input += chunk; });
21
+ process.stdin.on('end', () => {
22
+ clearTimeout(stdinTimeout);
23
+ try {
24
+ const data = JSON.parse(input);
25
+ const session = data.session_id || '';
26
+ const remaining = data.context_window?.remaining_percentage;
27
+
28
+ // Write bridge file for the context monitor PostToolUse hook
29
+ if (session && remaining != null) {
30
+ try {
31
+ const bridgePath = path.join(os.tmpdir(), `clancy-ctx-${session}.json`);
32
+ // Claude Code reserves ~16.5% for autocompact buffer.
33
+ // Normalise to show 100% at the usable limit (same as GSD).
34
+ const AUTO_COMPACT_BUFFER_PCT = 16.5;
35
+ const usableRemaining = Math.max(0, ((remaining - AUTO_COMPACT_BUFFER_PCT) / (100 - AUTO_COMPACT_BUFFER_PCT)) * 100);
36
+ const used = Math.max(0, Math.min(100, Math.round(100 - usableRemaining)));
37
+ fs.writeFileSync(bridgePath, JSON.stringify({
38
+ session_id: session,
39
+ remaining_percentage: remaining,
40
+ used_pct: used,
41
+ timestamp: Math.floor(Date.now() / 1000),
42
+ }));
43
+ } catch { /* bridge is best-effort */ }
44
+ }
45
+
46
+ // Build statusline output
47
+ const parts = [];
48
+
49
+ // Update available?
50
+ const claudeDir = process.env.CLAUDE_CONFIG_DIR || path.join(homeDir, '.claude');
51
+ const cacheFile = path.join(claudeDir, 'cache', 'clancy-update-check.json');
52
+ try {
53
+ const cache = JSON.parse(fs.readFileSync(cacheFile, 'utf8'));
54
+ if (cache.update_available) {
55
+ parts.push('\x1b[33m⬆ /clancy:update\x1b[0m');
56
+ }
57
+ } catch { /* cache missing is normal */ }
58
+
59
+ // Context bar
60
+ if (remaining != null) {
61
+ const AUTO_COMPACT_BUFFER_PCT = 16.5;
62
+ const usableRemaining = Math.max(0, ((remaining - AUTO_COMPACT_BUFFER_PCT) / (100 - AUTO_COMPACT_BUFFER_PCT)) * 100);
63
+ const used = Math.max(0, Math.min(100, Math.round(100 - usableRemaining)));
64
+ const filled = Math.floor(used / 10);
65
+ const bar = '█'.repeat(filled) + '░'.repeat(10 - filled);
66
+
67
+ let coloredBar;
68
+ if (used < 50) coloredBar = `\x1b[32m${bar} ${used}%\x1b[0m`;
69
+ else if (used < 65) coloredBar = `\x1b[33m${bar} ${used}%\x1b[0m`;
70
+ else if (used < 80) coloredBar = `\x1b[38;5;208m${bar} ${used}%\x1b[0m`;
71
+ else coloredBar = `\x1b[5;31m💀 ${bar} ${used}%\x1b[0m`;
72
+
73
+ parts.push(`\x1b[2mClancy\x1b[0m ${coloredBar}`);
74
+ } else {
75
+ parts.push('\x1b[2mClancy\x1b[0m');
76
+ }
77
+
78
+ process.stdout.write(parts.join(' │ '));
79
+ } catch {
80
+ process.exit(0);
81
+ }
82
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "chief-clancy",
3
- "version": "0.1.7",
3
+ "version": "0.2.0-beta.2",
4
4
  "description": "Autonomous, board-driven development for Claude Code — scaffolds docs, integrates Kanban boards, runs tickets in a loop.",
5
5
  "keywords": [
6
6
  "claude",
@@ -29,6 +29,7 @@
29
29
  "main": "bin/install.js",
30
30
  "files": [
31
31
  "bin/",
32
+ "hooks/",
32
33
  "src/",
33
34
  "registry/"
34
35
  ],
@@ -0,0 +1,14 @@
1
+ # /clancy:dry-run
2
+
3
+ Preview which ticket Clancy would pick up next — no changes made.
4
+
5
+ Shows:
6
+ - The ticket that would be picked up (key, summary, epic)
7
+ - The target branch and feature branch that would be created
8
+ - Full preflight checks — catches config issues early
9
+
10
+ Nothing is written, no git operations run, Claude is not invoked.
11
+
12
+ @.claude/clancy/workflows/once.md
13
+
14
+ Run the once workflow with `--dry-run` as documented above. Treat this invocation as if the user passed `--dry-run`.
@@ -20,6 +20,7 @@ integration, structured codebase docs, and a git workflow built for team develop
20
20
  | `/clancy:run` | Run in loop mode until queue is empty or MAX_ITERATIONS hit |
21
21
  | `/clancy:run 20` | Same, but override MAX_ITERATIONS to 20 for this session |
22
22
  | `/clancy:once` | Pick up one ticket and stop — good for first runs and debugging |
23
+ | `/clancy:dry-run` | Preview next ticket without making any changes |
23
24
  | `/clancy:status` | Show next tickets without running — read-only board check |
24
25
  | `/clancy:review` | Score next ticket (0–100%) with actionable recommendations |
25
26
  | `/clancy:logs` | Format and display .clancy/progress.txt |
@@ -35,7 +36,7 @@ integration, structured codebase docs, and a git workflow built for team develop
35
36
 
36
37
  1. Run `/clancy:init` to connect your Kanban board and scaffold .clancy/
37
38
  2. Run `/clancy:map-codebase` to generate codebase docs (or say yes during init)
38
- 3. Run `/clancy:once` to watch your first ticket — then go AFK with `/clancy:run`
39
+ 3. Run `/clancy:dry-run` to preview the first ticket, then `/clancy:once` to run it — then go AFK with `/clancy:run`
39
40
 
40
41
  Clancy picks one ticket per loop, fresh context every iteration. No context rot.
41
42
 
@@ -8,6 +8,10 @@ Good for:
8
8
  - Debugging a specific ticket
9
9
  - When you only have time for one ticket
10
10
 
11
+ Pass `--dry-run` to preview what Clancy would do without making any changes:
12
+ - Shows the ticket, epic, target branch, and feature branch
13
+ - Exits before any git operations or Claude invocation
14
+
11
15
  @.claude/clancy/workflows/once.md
12
16
 
13
17
  Run one ticket as documented in the workflow above.
@@ -48,6 +48,12 @@ MAX_ITERATIONS=5
48
48
  # PLAYWRIGHT_STORYBOOK_PORT=6006
49
49
  # PLAYWRIGHT_STARTUP_WAIT=15
50
50
 
51
+ # ─── Optional: Status transitions ────────────────────────────────────────────
52
+ # Move tickets automatically when Clancy picks up or completes them.
53
+ # Set to the exact status name shown in your Jira board column header.
54
+ # CLANCY_STATUS_IN_PROGRESS="In Progress"
55
+ # CLANCY_STATUS_DONE="Done"
56
+
51
57
  # ─── Optional: Notifications ──────────────────────────────────────────────────
52
58
  # Webhook URL for Slack or Teams notifications on ticket completion
53
59
  # CLANCY_NOTIFY_WEBHOOK=https://hooks.slack.com/services/your/webhook/url
@@ -38,6 +38,12 @@ MAX_ITERATIONS=20
38
38
  # PLAYWRIGHT_STORYBOOK_PORT=6006
39
39
  # PLAYWRIGHT_STARTUP_WAIT=15
40
40
 
41
+ # ─── Optional: Status transitions ────────────────────────────────────────────
42
+ # Move issues automatically when Clancy picks up or completes them.
43
+ # Set to the exact workflow state name shown in your Linear board column header.
44
+ # CLANCY_STATUS_IN_PROGRESS="In Progress"
45
+ # CLANCY_STATUS_DONE="Done"
46
+
41
47
  # ─── Optional: Notifications ──────────────────────────────────────────────────
42
48
  # Webhook URL for Slack or Teams notifications on ticket completion
43
49
  # CLANCY_NOTIFY_WEBHOOK=https://hooks.slack.com/services/your/webhook/url
@@ -3,19 +3,26 @@
3
3
 
4
4
  This project uses Clancy for autonomous ticket-driven development.
5
5
 
6
+ ### Version check
7
+ At the start of each session, read `~/.claude/cache/clancy-update-check.json` (use the Bash tool). If it exists and `update_available` is true, display exactly:
8
+ `⚠ Clancy update available: v{installed} → v{latest}. Run /clancy:update to upgrade.`
9
+ Then continue normally. If the file is missing or unreadable, continue silently.
10
+
6
11
  ### Docs
7
- Before every run, read all docs in `.clancy/docs/`:
12
+ Before every run, read these core docs from `.clancy/docs/`:
8
13
  - STACK.md — tech stack and dependencies
9
- - INTEGRATIONS.md — external services and APIs
10
14
  - ARCHITECTURE.md — system design and data flow
11
15
  - CONVENTIONS.md — code style and patterns
12
- - TESTING.md — test approach and coverage expectations
13
16
  - GIT.md — branching, commit format, merge strategy
14
- - DESIGN-SYSTEM.md — tokens, components, visual conventions
15
- - ACCESSIBILITY.md — WCAG requirements and ARIA patterns
16
17
  - DEFINITION-OF-DONE.md — checklist before marking a ticket complete
17
18
  - CONCERNS.md — known risks, tech debt, things to avoid
18
19
 
20
+ Also read these if relevant to the ticket:
21
+ - INTEGRATIONS.md — if the ticket touches external APIs, services, or authentication
22
+ - TESTING.md — if the ticket involves tests, specs, or coverage requirements
23
+ - DESIGN-SYSTEM.md — if the ticket touches UI, components, styles, or visual design
24
+ - ACCESSIBILITY.md — if the ticket touches accessibility, ARIA, or WCAG requirements
25
+
19
26
  ### Executability check
20
27
 
21
28
  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.