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 +44 -0
- package/CONTRIBUTING.md +59 -0
- package/README.md +6 -0
- package/VERSION +1 -1
- package/bin/install.js +252 -62
- package/hooks/flow-check-update.js +80 -33
- package/hooks/flow-statusline.js +30 -3
- package/package.json +7 -2
- package/skills/flow-done.md +2 -0
- package/skills/flow-spec.md +3 -2
- package/skills/flow-update.md +11 -2
- package/templates/lessons.md.template +10 -0
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`
|
package/CONTRIBUTING.md
ADDED
|
@@ -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
|
+
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
|
|
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
|
-
//
|
|
75
|
-
|
|
76
|
-
|
|
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
|
-
//
|
|
80
|
-
const
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
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
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
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
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
fs.
|
|
104
|
-
if (fs.existsSync(
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
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.
|
|
207
|
+
console.error('Rollback complete.');
|
|
110
208
|
}
|
|
111
209
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
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
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
//
|
|
134
|
-
const
|
|
135
|
-
|
|
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
|
|
140
|
-
/flow:init
|
|
141
|
-
/flow:spec
|
|
142
|
-
/flow:go
|
|
143
|
-
/flow:done
|
|
144
|
-
/flow:status
|
|
145
|
-
/flow:task
|
|
146
|
-
/flow:update
|
|
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
|
-
|
|
16
|
-
|
|
17
|
-
|
|
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
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
34
|
+
try {
|
|
35
|
+
// Ensure cache directory exists
|
|
36
|
+
if (!fs.existsSync(cacheDir)) {
|
|
37
|
+
fs.mkdirSync(cacheDir, { recursive: true });
|
|
38
|
+
}
|
|
24
39
|
|
|
25
|
-
|
|
26
|
-
const
|
|
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
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
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
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
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
|
+
}
|
package/hooks/flow-statusline.js
CHANGED
|
@@ -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.
|
|
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
|
}
|
package/skills/flow-done.md
CHANGED
|
@@ -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`
|
package/skills/flow-spec.md
CHANGED
|
@@ -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
|
|
20
|
-
- Use Grep for
|
|
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
|
|
package/skills/flow-update.md
CHANGED
|
@@ -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:
|
|
49
|
+
## Step 4: Show what's new
|
|
50
50
|
|
|
51
|
-
|
|
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
|
|