flow-cc 0.1.1 → 0.2.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/CHANGELOG.md ADDED
@@ -0,0 +1,44 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [0.2.0] - 2026-02-11
9
+
10
+ ### Added
11
+ - `CHANGELOG.md` in Keep a Changelog format
12
+ - `CONTRIBUTING.md` with skill file format docs and testing expectations
13
+ - Example lesson in `templates/lessons.md.template` showing PATTERN/CAUSE/FIX/RULE format
14
+ - Node.js version check (requires >= 18) with clear error message
15
+ - Write permission check before install starts
16
+ - Rollback on install failure — tracks created files and cleans up
17
+ - Post-install verification — checks key files exist after copy
18
+ - `--verify` flag for install health check without modifying anything
19
+ - `engines` field in package.json (`node >= 18`)
20
+ - Error logging for hooks — writes to `~/.claude/hooks/flow-error.log` (capped at 50KB)
21
+ - Context size limits in `/flow:spec` — caps codebase scan at 50 files, focused mode for 500+ file repos
22
+
23
+ ### Fixed
24
+ - `.gitignore` now has proper Node.js entries (was just `nul`)
25
+ - `/flow:done` archive step now creates `.planning/archive/` if missing and skips gracefully when no `.planning/` exists
26
+ - `/flow:update` now shows changelog for the new version before confirming update
27
+
28
+ ## [0.1.1] - 2025-05-01
29
+
30
+ ### Changed
31
+ - Updated package.json metadata (description, keywords, repository links)
32
+ - Polished README with badges and install instructions
33
+ - Added MIT license file
34
+
35
+ ## [0.1.0] - 2025-05-01
36
+
37
+ ### Added
38
+ - Initial release of Flow plugin for Claude Code
39
+ - 8 skills: init, spec, go, done, status, task, intro, update
40
+ - 2 hooks: statusline display, background update checker
41
+ - 4 templates: CLAUDE.md, STATE.md, ROADMAP.md, lessons.md
42
+ - One-command installer via `npx flow-cc`
43
+ - Automatic statusLine configuration
44
+ - Uninstall support via `npx flow-cc --uninstall`
@@ -0,0 +1,59 @@
1
+ # Contributing to Flow
2
+
3
+ Thanks for your interest in contributing! Flow is a structured workflow system for Claude Code, and contributions are welcome.
4
+
5
+ ## Reporting Bugs
6
+
7
+ Open an issue at [github.com/troyhoffman-oss/flow-plugin/issues](https://github.com/troyhoffman-oss/flow-plugin/issues) with:
8
+
9
+ - Steps to reproduce
10
+ - Expected vs. actual behavior
11
+ - Your OS and Node.js version
12
+ - Claude Code version (if relevant)
13
+
14
+ ## Suggesting Features
15
+
16
+ Open an issue with the `enhancement` label. Describe:
17
+
18
+ - The problem you're trying to solve
19
+ - Your proposed solution
20
+ - Any alternatives you've considered
21
+
22
+ ## Submitting Pull Requests
23
+
24
+ 1. Fork the repo and create a branch from `master`
25
+ 2. Make your changes
26
+ 3. Test the installer: `node bin/install.js` from the repo root
27
+ 4. Verify skills load in Claude Code (run `/flow:intro`)
28
+ 5. Open a PR with a clear description of what changed and why
29
+
30
+ ### Skill File Format
31
+
32
+ Skills live in `skills/` as Markdown files with YAML frontmatter:
33
+
34
+ ```markdown
35
+ ---
36
+ name: flow:example
37
+ description: One-line description
38
+ user_invocable: true
39
+ ---
40
+
41
+ # /flow:example — Title
42
+
43
+ Instructions for Claude Code to follow when this skill is invoked.
44
+ ```
45
+
46
+ ### Testing Expectations
47
+
48
+ - Run `node bin/install.js` and verify all files land in `~/.claude/`
49
+ - Run `node bin/install.js --uninstall` and verify clean removal
50
+ - Run `node bin/install.js --verify` to check install health
51
+ - Test on both Windows and macOS/Linux if possible
52
+
53
+ ## Architecture
54
+
55
+ See [DESIGN.md](DESIGN.md) for architecture decisions and system design.
56
+
57
+ ## Code of Conduct
58
+
59
+ Be kind, constructive, and respectful. We're all here to build better tools.
package/README.md CHANGED
@@ -121,6 +121,12 @@ Flow uses the same `.planning/` directory structure as [GSD](https://github.com/
121
121
 
122
122
  [Claude Code](https://docs.anthropic.com/en/docs/claude-code) CLI with skills support.
123
123
 
124
+ ## Links
125
+
126
+ - [Changelog](CHANGELOG.md) — what changed in each version
127
+ - [Contributing](CONTRIBUTING.md) — how to report bugs, suggest features, submit PRs
128
+ - [Design](DESIGN.md) — architecture decisions and system design
129
+
124
130
  ## License
125
131
 
126
132
  MIT
package/VERSION CHANGED
@@ -1 +1 @@
1
- 0.1.1
1
+ 0.2.0
package/bin/install.js CHANGED
@@ -1,11 +1,18 @@
1
- #!/usr/bin/env node
1
+ #!/usr/bin/env node
2
2
  // Flow plugin installer for Claude Code
3
- // Usage: npx flow-cc [--uninstall]
3
+ // Usage: npx flow-cc [--uninstall] [--verify]
4
4
 
5
5
  const fs = require('fs');
6
6
  const path = require('path');
7
7
  const os = require('os');
8
8
 
9
+ // ---------- Node.js version check ----------
10
+ const nodeMajor = parseInt(process.versions.node.split('.')[0], 10);
11
+ if (nodeMajor < 18) {
12
+ console.error('Flow requires Node.js 18 or later. You have ' + process.version);
13
+ process.exit(1);
14
+ }
15
+
9
16
  const homeDir = os.homedir();
10
17
  const claudeDir = path.join(homeDir, '.claude');
11
18
  const commandsDir = path.join(claudeDir, 'commands', 'flow');
@@ -21,6 +28,93 @@ const templatesDir = path.join(pkgRoot, 'templates');
21
28
  const versionFile = path.join(pkgRoot, 'VERSION');
22
29
 
23
30
  const uninstall = process.argv.includes('--uninstall') || process.argv.includes('-u');
31
+ const verify = process.argv.includes('--verify') || process.argv.includes('-v');
32
+
33
+ // ---------- Verify ----------
34
+ if (verify) {
35
+ console.log('Flow install health check:\n');
36
+
37
+ let passed = 0;
38
+ const total = 5;
39
+
40
+ // 1. Skills installed
41
+ try {
42
+ const files = fs.existsSync(commandsDir)
43
+ ? fs.readdirSync(commandsDir).filter(f => f.endsWith('.md'))
44
+ : [];
45
+ if (files.length > 0) {
46
+ console.log(` \u2713 Skills installed (${files.length} files)`);
47
+ passed++;
48
+ } else {
49
+ console.log(' \u2717 Skills not installed (0 .md files in commands/flow/)');
50
+ }
51
+ } catch (e) {
52
+ console.log(' \u2717 Skills not installed (cannot read commands/flow/)');
53
+ }
54
+
55
+ // 2. Hooks installed
56
+ try {
57
+ const hookFiles = ['flow-check-update.js', 'flow-statusline.js'];
58
+ const found = hookFiles.filter(h => fs.existsSync(path.join(hooksDir, h)));
59
+ if (found.length === 2) {
60
+ console.log(' \u2713 Hooks installed (2 files)');
61
+ passed++;
62
+ } else {
63
+ console.log(` \u2717 Hooks incomplete (${found.length}/2 files)`);
64
+ }
65
+ } catch (e) {
66
+ console.log(' \u2717 Hooks not installed');
67
+ }
68
+
69
+ // 3. VERSION file
70
+ try {
71
+ const vPath = path.join(commandsDir, 'VERSION');
72
+ if (fs.existsSync(vPath)) {
73
+ const ver = fs.readFileSync(vPath, 'utf8').trim();
74
+ console.log(` \u2713 VERSION file present (${ver})`);
75
+ passed++;
76
+ } else {
77
+ console.log(' \u2717 VERSION file missing');
78
+ }
79
+ } catch (e) {
80
+ console.log(' \u2717 VERSION file missing');
81
+ }
82
+
83
+ // 4. Templates installed
84
+ try {
85
+ const tDir = path.join(commandsDir, 'templates');
86
+ const files = fs.existsSync(tDir) ? fs.readdirSync(tDir) : [];
87
+ if (files.length > 0) {
88
+ console.log(` \u2713 Templates installed (${files.length} files)`);
89
+ passed++;
90
+ } else {
91
+ console.log(' \u2717 Templates not installed (0 files)');
92
+ }
93
+ } catch (e) {
94
+ console.log(' \u2717 Templates not installed');
95
+ }
96
+
97
+ // 5. StatusLine configured
98
+ try {
99
+ if (fs.existsSync(settingsPath)) {
100
+ const settings = JSON.parse(fs.readFileSync(settingsPath, 'utf8'));
101
+ if (settings.statusLine && typeof settings.statusLine.command === 'string' &&
102
+ settings.statusLine.command.includes('flow-statusline')) {
103
+ console.log(' \u2713 StatusLine configured');
104
+ passed++;
105
+ } else {
106
+ console.log(' \u2717 StatusLine not configured');
107
+ }
108
+ } else {
109
+ console.log(' \u2717 StatusLine not configured (no settings.json)');
110
+ }
111
+ } catch (e) {
112
+ console.log(' \u2717 StatusLine not configured');
113
+ }
114
+
115
+ console.log(`\nResult: ${passed}/${total} checks passed`);
116
+ process.exit(passed === total ? 0 : 1);
117
+ }
24
118
 
25
119
  // ---------- Uninstall ----------
26
120
  if (uninstall) {
@@ -60,7 +154,7 @@ if (uninstall) {
60
154
  console.log(' Removed statusLine from ~/.claude/settings.json');
61
155
  }
62
156
  } catch (e) {
63
- // settings.json parse error leave it alone
157
+ // settings.json parse error -- leave it alone
64
158
  }
65
159
  }
66
160
 
@@ -71,79 +165,175 @@ if (uninstall) {
71
165
  // ---------- Install ----------
72
166
  console.log('Installing Flow plugin...\n');
73
167
 
74
- // 1. Create directories
75
- for (const dir of [commandsDir, hooksDir, cacheDir]) {
76
- fs.mkdirSync(dir, { recursive: true });
168
+ // ---------- Write permission check ----------
169
+ const permTestDir = fs.existsSync(claudeDir) ? claudeDir : path.dirname(claudeDir);
170
+ const permTestFile = path.join(permTestDir, '.flow-perm-test-' + process.pid);
171
+ try {
172
+ fs.writeFileSync(permTestFile, 'test');
173
+ fs.unlinkSync(permTestFile);
174
+ } catch (e) {
175
+ console.error('Cannot write to ' + claudeDir + ' \u2014 check permissions.');
176
+ process.exit(1);
77
177
  }
78
178
 
79
- // 2. Copy skills: skills/flow-*.md → commands/flow/*.md (strip "flow-" prefix)
80
- const skillFiles = fs.readdirSync(skillsDir).filter(f => f.startsWith('flow-') && f.endsWith('.md'));
81
- for (const file of skillFiles) {
82
- const dest = file.replace(/^flow-/, '');
83
- fs.copyFileSync(path.join(skillsDir, file), path.join(commandsDir, dest));
179
+ // ---------- Rollback tracking ----------
180
+ const created = []; // paths created during install (files and dirs we created fresh)
181
+
182
+ function trackFile(filePath) {
183
+ created.push({ type: 'file', path: filePath });
84
184
  }
85
- console.log(` Installed ${skillFiles.length} skills → ~/.claude/commands/flow/`);
86
185
 
87
- // 3. Copy hooks
88
- const hookFiles = ['flow-check-update.js', 'flow-statusline.js'];
89
- for (const hook of hookFiles) {
90
- const src = path.join(srcHooksDir, hook);
91
- if (fs.existsSync(src)) {
92
- fs.copyFileSync(src, path.join(hooksDir, hook));
186
+ function trackDir(dirPath) {
187
+ // Only track if we actually created it (didn't exist before)
188
+ if (!fs.existsSync(dirPath)) {
189
+ created.push({ type: 'dir', path: dirPath });
93
190
  }
94
191
  }
95
- console.log(' Installed hooks → ~/.claude/hooks/');
96
192
 
97
- // 4. Copy VERSION
98
- fs.copyFileSync(versionFile, path.join(commandsDir, 'VERSION'));
99
- console.log(' Installed VERSION ~/.claude/commands/flow/VERSION');
100
-
101
- // 5. Copy templates
102
- const destTemplatesDir = path.join(commandsDir, 'templates');
103
- fs.mkdirSync(destTemplatesDir, { recursive: true });
104
- if (fs.existsSync(templatesDir)) {
105
- const templateFiles = fs.readdirSync(templatesDir);
106
- for (const file of templateFiles) {
107
- fs.copyFileSync(path.join(templatesDir, file), path.join(destTemplatesDir, file));
193
+ function rollback() {
194
+ console.error('\nRolling back partial install...');
195
+ for (let i = created.length - 1; i >= 0; i--) {
196
+ const item = created[i];
197
+ try {
198
+ if (item.type === 'file' && fs.existsSync(item.path)) {
199
+ fs.unlinkSync(item.path);
200
+ } else if (item.type === 'dir' && fs.existsSync(item.path)) {
201
+ fs.rmSync(item.path, { recursive: true });
202
+ }
203
+ } catch (e) {
204
+ // Best-effort cleanup
205
+ }
108
206
  }
109
- console.log(` Installed ${templateFiles.length} templates → ~/.claude/commands/flow/templates/`);
207
+ console.error('Rollback complete.');
110
208
  }
111
209
 
112
- // 6. Merge statusLine into settings.json
113
- let settings = {};
114
- if (fs.existsSync(settingsPath)) {
115
- try {
116
- settings = JSON.parse(fs.readFileSync(settingsPath, 'utf8'));
117
- } catch (e) {
118
- // Corrupted settings — start fresh but warn
119
- console.log(' Warning: could not parse existing settings.json, preserving as backup');
120
- fs.copyFileSync(settingsPath, settingsPath + '.bak');
210
+ try {
211
+ // 1. Create directories
212
+ for (const dir of [commandsDir, hooksDir, cacheDir]) {
213
+ trackDir(dir);
214
+ fs.mkdirSync(dir, { recursive: true });
121
215
  }
122
- }
123
- settings.statusLine = {
124
- type: 'command',
125
- command: `node "${path.join(hooksDir, 'flow-statusline.js')}"`
126
- };
127
- fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\n');
128
- console.log(' Configured statusLine in ~/.claude/settings.json');
129
-
130
- // 7. Write .source breadcrumb (for dev/setup.sh compat)
131
- fs.writeFileSync(path.join(commandsDir, '.source'), pkgRoot + '\n');
132
-
133
- // Done
134
- const version = fs.readFileSync(versionFile, 'utf8').trim();
135
- console.log(`
216
+
217
+ // 2. Copy skills: skills/flow-*.md -> commands/flow/*.md (strip "flow-" prefix)
218
+ const skillFiles = fs.readdirSync(skillsDir).filter(f => f.startsWith('flow-') && f.endsWith('.md'));
219
+ for (const file of skillFiles) {
220
+ const dest = file.replace(/^flow-/, '');
221
+ const destPath = path.join(commandsDir, dest);
222
+ fs.copyFileSync(path.join(skillsDir, file), destPath);
223
+ trackFile(destPath);
224
+ }
225
+ console.log(` Installed ${skillFiles.length} skills \u2192 ~/.claude/commands/flow/`);
226
+
227
+ // 3. Copy hooks
228
+ const hookFiles = ['flow-check-update.js', 'flow-statusline.js'];
229
+ for (const hook of hookFiles) {
230
+ const src = path.join(srcHooksDir, hook);
231
+ if (fs.existsSync(src)) {
232
+ const destPath = path.join(hooksDir, hook);
233
+ fs.copyFileSync(src, destPath);
234
+ trackFile(destPath);
235
+ }
236
+ }
237
+ console.log(' Installed hooks \u2192 ~/.claude/hooks/');
238
+
239
+ // 4. Copy VERSION
240
+ const versionDest = path.join(commandsDir, 'VERSION');
241
+ fs.copyFileSync(versionFile, versionDest);
242
+ trackFile(versionDest);
243
+ console.log(' Installed VERSION \u2192 ~/.claude/commands/flow/VERSION');
244
+
245
+ // 5. Copy templates
246
+ const destTemplatesDir = path.join(commandsDir, 'templates');
247
+ trackDir(destTemplatesDir);
248
+ fs.mkdirSync(destTemplatesDir, { recursive: true });
249
+ if (fs.existsSync(templatesDir)) {
250
+ const templateFiles = fs.readdirSync(templatesDir);
251
+ for (const file of templateFiles) {
252
+ const destPath = path.join(destTemplatesDir, file);
253
+ fs.copyFileSync(path.join(templatesDir, file), destPath);
254
+ trackFile(destPath);
255
+ }
256
+ console.log(` Installed ${templateFiles.length} templates \u2192 ~/.claude/commands/flow/templates/`);
257
+ }
258
+
259
+ // 6. Merge statusLine into settings.json
260
+ let settings = {};
261
+ if (fs.existsSync(settingsPath)) {
262
+ try {
263
+ settings = JSON.parse(fs.readFileSync(settingsPath, 'utf8'));
264
+ } catch (e) {
265
+ // Corrupted settings -- start fresh but warn
266
+ console.log(' Warning: could not parse existing settings.json, preserving as backup');
267
+ fs.copyFileSync(settingsPath, settingsPath + '.bak');
268
+ }
269
+ }
270
+ settings.statusLine = {
271
+ type: 'command',
272
+ command: `node "${path.join(hooksDir, 'flow-statusline.js')}"`
273
+ };
274
+ fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\n');
275
+ trackFile(settingsPath);
276
+ console.log(' Configured statusLine in ~/.claude/settings.json');
277
+
278
+ // 7. Write .source breadcrumb (for dev/setup.sh compat)
279
+ const sourcePath = path.join(commandsDir, '.source');
280
+ fs.writeFileSync(sourcePath, pkgRoot + '\n');
281
+ trackFile(sourcePath);
282
+
283
+ // ---------- Post-install verification ----------
284
+ let warnings = 0;
285
+
286
+ // Check: at least 1 file in commandsDir
287
+ const installedSkills = fs.readdirSync(commandsDir).filter(f => f.endsWith('.md'));
288
+ if (installedSkills.length === 0) {
289
+ console.log(' Warning: no skill files found in commands/flow/');
290
+ warnings++;
291
+ }
292
+
293
+ // Check: both hook files
294
+ const installedHooks = hookFiles.filter(h => fs.existsSync(path.join(hooksDir, h)));
295
+ if (installedHooks.length < 2) {
296
+ console.log(` Warning: only ${installedHooks.length}/2 hook files installed`);
297
+ warnings++;
298
+ }
299
+
300
+ // Check: VERSION file
301
+ if (!fs.existsSync(path.join(commandsDir, 'VERSION'))) {
302
+ console.log(' Warning: VERSION file not found after install');
303
+ warnings++;
304
+ }
305
+
306
+ // Check: templates directory with at least 1 file
307
+ const tplDir = path.join(commandsDir, 'templates');
308
+ const tplFiles = fs.existsSync(tplDir) ? fs.readdirSync(tplDir) : [];
309
+ if (tplFiles.length === 0) {
310
+ console.log(' Warning: no template files found after install');
311
+ warnings++;
312
+ }
313
+
314
+ if (warnings > 0) {
315
+ console.log(` (${warnings} verification warning${warnings > 1 ? 's' : ''} — install may be incomplete)`);
316
+ }
317
+
318
+ // Done
319
+ const version = fs.readFileSync(versionFile, 'utf8').trim();
320
+ console.log(`
136
321
  Flow v${version} installed successfully!
137
322
 
138
323
  Commands available:
139
- /flow:intro Learn the Flow workflow
140
- /flow:init Start a new project or milestone
141
- /flow:spec Spec interview executable PRD
142
- /flow:go Execute next phase with agent teams
143
- /flow:done Session-end documentation
144
- /flow:status Quick orientation
145
- /flow:task Lightweight task execution
146
- /flow:update Update Flow to latest version
324
+ /flow:intro \u2014 Learn the Flow workflow
325
+ /flow:init \u2014 Start a new project or milestone
326
+ /flow:spec \u2014 Spec interview \u2192 executable PRD
327
+ /flow:go \u2014 Execute next phase with agent teams
328
+ /flow:done \u2014 Session-end documentation
329
+ /flow:status \u2014 Quick orientation
330
+ /flow:task \u2014 Lightweight task execution
331
+ /flow:update \u2014 Update Flow to latest version
147
332
 
148
333
  Get started: run /flow:intro in any Claude Code session.
149
334
  `);
335
+ } catch (err) {
336
+ rollback();
337
+ console.error('\nInstall failed: ' + err.message);
338
+ process.exit(1);
339
+ }
@@ -11,43 +11,90 @@ const homeDir = os.homedir();
11
11
  const cacheDir = path.join(homeDir, '.claude', 'cache');
12
12
  const cacheFile = path.join(cacheDir, 'flow-update-check.json');
13
13
  const versionFile = path.join(homeDir, '.claude', 'commands', 'flow', 'VERSION');
14
+ const errorLog = path.join(homeDir, '.claude', 'hooks', 'flow-error.log');
14
15
 
15
- // Ensure cache directory exists
16
- if (!fs.existsSync(cacheDir)) {
17
- fs.mkdirSync(cacheDir, { recursive: true });
16
+ function logError(context, err) {
17
+ try {
18
+ const line = `[${new Date().toISOString()}] flow-check-update: ${context}: ${err.message || err}\n`;
19
+ // Cap log at 50KB — truncate oldest entries
20
+ if (fs.existsSync(errorLog)) {
21
+ const stat = fs.statSync(errorLog);
22
+ if (stat.size > 50 * 1024) {
23
+ const content = fs.readFileSync(errorLog, 'utf8');
24
+ const lines = content.split('\n');
25
+ fs.writeFileSync(errorLog, lines.slice(Math.floor(lines.length / 2)).join('\n'));
26
+ }
27
+ }
28
+ fs.appendFileSync(errorLog, line);
29
+ } catch (_) {
30
+ // Logging must never throw
31
+ }
18
32
  }
19
33
 
20
- // Run check in background (spawn detached process, windowsHide prevents console flash)
21
- const child = spawn(process.execPath, ['-e', `
22
- const fs = require('fs');
23
- const { execSync } = require('child_process');
34
+ try {
35
+ // Ensure cache directory exists
36
+ if (!fs.existsSync(cacheDir)) {
37
+ fs.mkdirSync(cacheDir, { recursive: true });
38
+ }
24
39
 
25
- const cacheFile = ${JSON.stringify(cacheFile)};
26
- const versionFile = ${JSON.stringify(versionFile)};
40
+ // Run check in background (spawn detached process, windowsHide prevents console flash)
41
+ const child = spawn(process.execPath, ['-e', `
42
+ const fs = require('fs');
43
+ const { execSync } = require('child_process');
27
44
 
28
- let installed = '0.0.0';
29
- try {
30
- if (fs.existsSync(versionFile)) {
31
- installed = fs.readFileSync(versionFile, 'utf8').trim();
45
+ const cacheFile = ${JSON.stringify(cacheFile)};
46
+ const versionFile = ${JSON.stringify(versionFile)};
47
+ const errorLog = ${JSON.stringify(errorLog)};
48
+
49
+ function logError(context, err) {
50
+ try {
51
+ const line = '[' + new Date().toISOString() + '] flow-check-update(child): ' + context + ': ' + (err.message || err) + '\\n';
52
+ if (fs.existsSync(errorLog)) {
53
+ const stat = fs.statSync(errorLog);
54
+ if (stat.size > 50 * 1024) {
55
+ const content = fs.readFileSync(errorLog, 'utf8');
56
+ const lines = content.split('\\n');
57
+ fs.writeFileSync(errorLog, lines.slice(Math.floor(lines.length / 2)).join('\\n'));
58
+ }
59
+ }
60
+ fs.appendFileSync(errorLog, line);
61
+ } catch (_) {}
32
62
  }
33
- } catch (e) {}
34
63
 
35
- let latest = null;
36
- try {
37
- latest = execSync('npm view flow-cc version', { encoding: 'utf8', timeout: 10000, windowsHide: true }).trim();
38
- } catch (e) {}
39
-
40
- const result = {
41
- update_available: latest && installed !== latest,
42
- installed,
43
- latest: latest || 'unknown',
44
- checked: Math.floor(Date.now() / 1000)
45
- };
46
-
47
- fs.writeFileSync(cacheFile, JSON.stringify(result));
48
- `], {
49
- stdio: 'ignore',
50
- windowsHide: true
51
- });
52
-
53
- child.unref();
64
+ try {
65
+ let installed = '0.0.0';
66
+ try {
67
+ if (fs.existsSync(versionFile)) {
68
+ installed = fs.readFileSync(versionFile, 'utf8').trim();
69
+ }
70
+ } catch (e) {
71
+ logError('read-version', e);
72
+ }
73
+
74
+ let latest = null;
75
+ try {
76
+ latest = execSync('npm view flow-cc version', { encoding: 'utf8', timeout: 10000, windowsHide: true }).trim();
77
+ } catch (e) {
78
+ logError('npm-view', e);
79
+ }
80
+
81
+ const result = {
82
+ update_available: latest && installed !== latest,
83
+ installed,
84
+ latest: latest || 'unknown',
85
+ checked: Math.floor(Date.now() / 1000)
86
+ };
87
+
88
+ fs.writeFileSync(cacheFile, JSON.stringify(result));
89
+ } catch (e) {
90
+ logError('main', e);
91
+ }
92
+ `], {
93
+ stdio: 'ignore',
94
+ windowsHide: true
95
+ });
96
+
97
+ child.unref();
98
+ } catch (e) {
99
+ logError('spawn', e);
100
+ }
@@ -7,6 +7,25 @@ const path = require('path');
7
7
  const os = require('os');
8
8
 
9
9
  const homeDir = os.homedir();
10
+ const errorLog = path.join(homeDir, '.claude', 'hooks', 'flow-error.log');
11
+
12
+ function logError(context, err) {
13
+ try {
14
+ const line = `[${new Date().toISOString()}] flow-statusline: ${context}: ${err.message || err}\n`;
15
+ // Cap log at 50KB — truncate oldest entries
16
+ if (fs.existsSync(errorLog)) {
17
+ const stat = fs.statSync(errorLog);
18
+ if (stat.size > 50 * 1024) {
19
+ const content = fs.readFileSync(errorLog, 'utf8');
20
+ const lines = content.split('\n');
21
+ fs.writeFileSync(errorLog, lines.slice(Math.floor(lines.length / 2)).join('\n'));
22
+ }
23
+ }
24
+ fs.appendFileSync(errorLog, line);
25
+ } catch (_) {
26
+ // Logging must never throw
27
+ }
28
+ }
10
29
 
11
30
  // Read JSON from stdin (Claude Code statusline protocol)
12
31
  let input = '';
@@ -56,9 +75,13 @@ process.stdin.on('end', () => {
56
75
  const todos = JSON.parse(fs.readFileSync(path.join(todosDir, files[0].name), 'utf8'));
57
76
  const inProgress = todos.find(t => t.status === 'in_progress');
58
77
  if (inProgress) task = inProgress.activeForm || '';
59
- } catch (e) {}
78
+ } catch (e) {
79
+ logError('parse-todos', e);
80
+ }
60
81
  }
61
- } catch (e) {}
82
+ } catch (e) {
83
+ logError('read-todos', e);
84
+ }
62
85
  }
63
86
 
64
87
  // --- Flow update notification ---
@@ -78,6 +101,7 @@ process.stdin.on('end', () => {
78
101
  shouldCheck = true;
79
102
  }
80
103
  } catch (e) {
104
+ logError('parse-cache', e);
81
105
  shouldCheck = true;
82
106
  }
83
107
  } else {
@@ -96,7 +120,9 @@ process.stdin.on('end', () => {
96
120
  });
97
121
  child.unref();
98
122
  }
99
- } catch (e) {}
123
+ } catch (e) {
124
+ logError('spawn-update-check', e);
125
+ }
100
126
  }
101
127
 
102
128
  // --- Output ---
@@ -107,6 +133,7 @@ process.stdin.on('end', () => {
107
133
  process.stdout.write(`${flowUpdate}\x1b[2m${model}\x1b[0m \u2502 \x1b[2m${dirname}\x1b[0m${ctx}`);
108
134
  }
109
135
  } catch (e) {
136
+ logError('main', e);
110
137
  // Silent fail — statusline must never crash
111
138
  }
112
139
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "flow-cc",
3
- "version": "0.1.1",
3
+ "version": "0.2.0",
4
4
  "description": "Structured workflow system for Claude Code — spec interviews, agent-team execution, session handoffs, compounding knowledge",
5
5
  "author": "Troy Hoffman",
6
6
  "license": "MIT",
@@ -25,11 +25,16 @@
25
25
  "bin": {
26
26
  "flow-cc": "bin/install.js"
27
27
  },
28
+ "engines": {
29
+ "node": ">=18"
30
+ },
28
31
  "files": [
29
32
  "bin",
30
33
  "skills",
31
34
  "hooks",
32
35
  "templates",
33
- "VERSION"
36
+ "VERSION",
37
+ "CHANGELOG.md",
38
+ "CONTRIBUTING.md"
34
39
  ]
35
40
  }
@@ -65,6 +65,8 @@ Structure:
65
65
  - Mark completed phases with completion date
66
66
  - Ensure pending phases have enough detail that the next session can start with a one-line prompt
67
67
  - **Archive check:** If the current milestone is fully complete:
68
+ - If `.planning/` does not exist, skip archiving entirely — there's nothing to archive
69
+ - Create `.planning/archive/` if it doesn't already exist (use `mkdir -p` or equivalent)
68
70
  - Move milestone phase details to `.planning/archive/milestones-vX.md`
69
71
  - Keep only the summary row in the ROADMAP milestone table
70
72
  - Move `PRD.md` to `.planning/archive/PRD-vX.md`
@@ -16,8 +16,9 @@ You are executing the `/flow:spec` skill. This is the KEYSTONE skill of the flow
16
16
  2. Read `CLAUDE.md` — understand project rules and tech stack
17
17
  3. Read `PRD.md` if it exists — check for existing spec to build on
18
18
  4. **Codebase scan** (brownfield projects):
19
- - Use Glob to find: components, pages/routes, API endpoints, types, utilities, config files, database models
20
- - Use Grep for: export patterns, route definitions, key function signatures
19
+ - **Size check first:** Use Glob with `**/*` to estimate total file count. If > 500 files, switch to focused mode (see below).
20
+ - **Standard mode (≤ 500 files):** Use Glob to find components, pages/routes, API endpoints, types, utilities, config files, database models. Use Grep for export patterns, route definitions, key function signatures. **Cap at 50 files sampled** — prioritize entry points, config, and type definitions.
21
+ - **Focused mode (> 500 files):** Scan ONLY: package.json/config files, entry points (index.ts, main.ts, app.ts), route definitions, database schema/models, and type definition files. Skip component trees, test files, and generated code entirely.
21
22
  - Build internal summary: "Here's what exists that we can reuse"
22
23
  5. Print a brief context summary to the user: "Here's what I found in the codebase: [key components, patterns, data layer]. Starting the spec interview."
23
24
 
@@ -46,14 +46,23 @@ If installed version equals latest version, print this and stop:
46
46
  Flow is up to date (v<version>)
47
47
  ```
48
48
 
49
- ## Step 4: Confirm update
49
+ ## Step 4: Show what's new
50
50
 
51
- Print the available update and ask the user to confirm:
51
+ Fetch the CHANGELOG.md from the Flow repo to show the user what changed:
52
+
53
+ 1. Run: `npx -y -p flow-cc@<latest> node -e "const fs=require('fs'),p=require('path');try{console.log(fs.readFileSync(p.join(require.resolve('flow-cc/package.json'),'..','CHANGELOG.md'),'utf8'))}catch(e){console.log('CHANGELOG not available')}"`
54
+ 2. Parse the output and extract only the section for `v<latest>` (from the `## [<latest>]` heading to the next `## [` heading or end of file)
55
+ 3. Print:
52
56
 
53
57
  ```
54
58
  Update available: v<installed> → v<latest>
59
+
60
+ Here's what's new in v<latest>:
61
+ <extracted changelog section>
55
62
  ```
56
63
 
64
+ If the CHANGELOG fetch fails, just print the version line without the changelog section. Don't let a missing changelog block the update.
65
+
57
66
  Wait for user confirmation before proceeding.
58
67
 
59
68
  ## Step 5: Run update
@@ -5,6 +5,16 @@ Format: PATTERN → CAUSE → FIX → RULE
5
5
  ## Execution Patterns
6
6
  <!-- Lessons about workflow, delegation, verification -->
7
7
 
8
+ <!-- EXAMPLE (replace with real lessons as you work):
9
+
10
+ ### Agent context overflow on large files
11
+ - **PATTERN:** Spawned agent tried to read a 2000-line file and ran out of context before finishing its task
12
+ - **CAUSE:** Agent prompt didn't specify which lines/functions to read — it read the whole file
13
+ - **FIX:** Added explicit line ranges and function names to the agent prompt
14
+ - **RULE:** Always tell agents exactly which functions/sections to read. Never say "read file.ts" — say "read file.ts lines 50-120 (the handleSubmit function)"
15
+
16
+ END EXAMPLE -->
17
+
8
18
  ## Domain Knowledge
9
19
  <!-- Lessons about business logic, data models, user behavior -->
10
20