chief-clancy 0.1.5 → 0.1.7

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-34%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-51%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
@@ -188,7 +188,7 @@ Clancy also merges a section into your `CLAUDE.md` (or creates one) that tells C
188
188
 
189
189
  ## Optional enhancements
190
190
 
191
- Set during `/clancy:init` advanced setup, or by editing `.clancy/.env` directly.
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.
192
192
 
193
193
  ### Figma MCP
194
194
 
@@ -405,6 +405,8 @@ lsof -ti:5173 | xargs kill -9 # replace 5173 with your PLAYWRIGHT_DEV_PORT
405
405
 
406
406
  Or directly: `npx chief-clancy@latest`
407
407
 
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
+
408
410
  ---
409
411
 
410
412
  **Uninstalling?**
@@ -413,7 +415,7 @@ Or directly: `npx chief-clancy@latest`
413
415
  /clancy:uninstall
414
416
  ```
415
417
 
416
- Removes slash commands from your chosen location. Optionally removes `.clancy/` (credentials and docs). Never touches `CLAUDE.md`.
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).
417
419
 
418
420
  ---
419
421
 
package/bin/install.js CHANGED
@@ -48,6 +48,13 @@ async function choose(question, options, defaultChoice = 1) {
48
48
  return raw.trim() || String(defaultChoice);
49
49
  }
50
50
 
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
+
51
58
  function copyDir(src, dest) {
52
59
  // Use lstatSync (not statSync) to detect symlinks — statSync follows them and misreports
53
60
  if (fs.existsSync(dest)) {
@@ -64,6 +71,73 @@ function copyDir(src, dest) {
64
71
  }
65
72
  }
66
73
 
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
+
67
141
  async function main() {
68
142
  console.log('');
69
143
  console.log(blue(' ██████╗██╗ █████╗ ███╗ ██╗ ██████╗██╗ ██╗'));
@@ -115,14 +189,42 @@ async function main() {
115
189
  console.log(dim(` Installing to: ${dest}`));
116
190
 
117
191
  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
+
118
197
  if (fs.existsSync(dest) || fs.existsSync(workflowsDest)) {
119
198
  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
+
120
216
  const overwrite = await ask(blue(` Commands already exist at ${dest}. Overwrite? [y/N] `));
121
217
  if (!overwrite.trim().toLowerCase().startsWith('y')) {
122
218
  console.log('\n Aborted. No files changed.');
123
219
  rl.close();
124
220
  process.exit(0);
125
221
  }
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
+ }
126
228
  }
127
229
 
128
230
  copyDir(COMMANDS_SRC, dest);
@@ -149,6 +251,14 @@ async function main() {
149
251
  // Write VERSION file so /clancy:doctor and /clancy:update can read the installed version
150
252
  fs.writeFileSync(path.join(dest, 'VERSION'), PKG.version);
151
253
 
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
+ );
261
+
152
262
  console.log('');
153
263
  console.log(green(' ✓ Clancy installed successfully.'));
154
264
  console.log('');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "chief-clancy",
3
- "version": "0.1.5",
3
+ "version": "0.1.7",
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",
@@ -336,9 +336,50 @@ When updating a value:
336
336
 
337
337
  ---
338
338
 
339
+ ### Save as global defaults
340
+
341
+ At the bottom of the settings menu (before Exit), show:
342
+
343
+ ```
344
+ [{N}] Save as defaults save current settings for all future projects
345
+ ```
346
+
347
+ When selected:
348
+
349
+ 1. Read the current `.clancy/.env` and extract only the non-credential, non-board-specific settings:
350
+ - `MAX_ITERATIONS`
351
+ - `CLANCY_MODEL`
352
+ - `CLANCY_BASE_BRANCH`
353
+ - `PLAYWRIGHT_ENABLED`
354
+ - `PLAYWRIGHT_STARTUP_WAIT`
355
+
356
+ 2. Write these to `~/.clancy/defaults.json`:
357
+ ```json
358
+ {
359
+ "MAX_ITERATIONS": "5",
360
+ "CLANCY_MODEL": "claude-sonnet-4-6",
361
+ "CLANCY_BASE_BRANCH": "main"
362
+ }
363
+ ```
364
+
365
+ 3. Print: `✓ Defaults saved to ~/.clancy/defaults.json — new projects will inherit these settings.`
366
+
367
+ 4. Loop back to the settings menu.
368
+
369
+ **Never save credentials, board-specific settings (status filter, sprint, label), or webhook URLs to global defaults.**
370
+
371
+ ---
372
+
373
+ ## Step 6 — Load global defaults during init
374
+
375
+ When `/clancy:init` creates `.clancy/.env`, check if `~/.clancy/defaults.json` exists. If so, pre-populate the `.env` with those values instead of the built-in defaults. The user's answers during init still take priority — defaults are only used for settings that init doesn't ask about (max iterations, model, etc.).
376
+
377
+ ---
378
+
339
379
  ## Notes
340
380
 
341
381
  - All changes are written to `.clancy/.env` immediately after confirmation
342
382
  - Switching boards verifies credentials before making any changes — nothing is written if verification fails
343
383
  - `/clancy:init` remains available for a full re-setup (re-scaffolds scripts and docs)
344
384
  - This command never restarts any servers or triggers any ticket processing
385
+ - Global defaults (`~/.clancy/defaults.json`) are optional — if the file doesn't exist, built-in defaults are used
@@ -108,6 +108,9 @@ Extract only the entries between the installed version and the latest version. D
108
108
  - `.claude/commands/clancy/` will be replaced
109
109
  - `.claude/clancy/workflows/` will be replaced
110
110
 
111
+ If you've modified any Clancy files directly, they'll be automatically backed up
112
+ to `.claude/clancy/local-patches/` before overwriting.
113
+
111
114
  Your project files are preserved:
112
115
  - `.clancy/` project folder (scripts, docs, .env, progress log) ✓
113
116
  - `CLAUDE.md` ✓
@@ -167,6 +170,31 @@ View full changelog: github.com/Pushedskydiver/clancy/blob/main/CHANGELOG.md
167
170
 
168
171
  ---
169
172
 
173
+ ## Step 6 — Check for local patches
174
+
175
+ After the update completes, check if the installer backed up any locally modified files:
176
+
177
+ Check for `.claude/clancy/local-patches/backup-meta.json` (local install) or `~/.claude/clancy/local-patches/backup-meta.json` (global install).
178
+
179
+ **If patches were found:**
180
+
181
+ ```
182
+ Local patches were backed up before the update.
183
+ Your modified files are in .claude/clancy/local-patches/
184
+
185
+ To review what changed:
186
+ Compare each file in local-patches/ against its counterpart in
187
+ .claude/commands/clancy/ or .claude/clancy/workflows/ and manually
188
+ reapply any customisations you want to keep.
189
+
190
+ Backed up files:
191
+ {list from backup-meta.json}
192
+ ```
193
+
194
+ **If no patches:** Continue normally (no message needed).
195
+
196
+ ---
197
+
170
198
  ## Notes
171
199
 
172
200
  - If the user installed globally, the update applies globally