create-issflow 1.0.2 → 1.1.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.
Files changed (42) hide show
  1. package/README.md +61 -56
  2. package/bin/cli.js +269 -259
  3. package/package.json +32 -28
  4. package/template/.claude/agents/debugger.md +47 -47
  5. package/template/.claude/agents/e2e-runner.md +66 -66
  6. package/template/.claude/agents/implementer.md +79 -75
  7. package/template/.claude/agents/planner.md +93 -71
  8. package/template/.claude/agents/researcher.md +103 -103
  9. package/template/.claude/agents/synthesizer.md +78 -72
  10. package/template/.claude/agents/test-author.md +70 -70
  11. package/template/.claude/commands/change-request.md +53 -0
  12. package/template/.claude/commands/log-decision.md +33 -33
  13. package/template/.claude/commands/log-issue.md +28 -28
  14. package/template/.claude/commands/overview.md +114 -99
  15. package/template/.claude/commands/phase.md +230 -202
  16. package/template/.claude/commands/propose.md +71 -0
  17. package/template/.claude/commands/quick.md +30 -30
  18. package/template/.claude/commands/replan.md +68 -63
  19. package/template/.claude/commands/store-wisdom.md +195 -195
  20. package/template/.claude/commands/synthesize.md +26 -26
  21. package/template/.claude/commands/unstuck.md +40 -40
  22. package/template/.claude/hooks/pre-compact.js +42 -0
  23. package/template/.claude/hooks/session-start.js +137 -0
  24. package/template/.claude/hooks/subagent-stop.js +18 -0
  25. package/template/.claude/istartsoft-flow/METHODOLOGY.md +403 -229
  26. package/template/.claude/skills/caveman/SKILL.md +39 -39
  27. package/template/.claude/skills/code-standards/SKILL.md +61 -0
  28. package/template/.claude/skills/code-standards/references/architecture.md +61 -0
  29. package/template/.claude/skills/code-standards/references/naming.md +60 -0
  30. package/template/.claude/skills/grill-me/SKILL.md +31 -10
  31. package/template/.claude/skills/karpathy-guidelines/SKILL.md +34 -34
  32. package/template/.claude/skills/security/SKILL.md +70 -0
  33. package/template/.claude/skills/security/references/pentest-checklist.md +46 -0
  34. package/template/.claude/skills/security/references/secure-coding.md +50 -0
  35. package/template/.claude/skills/security/references/standards.md +60 -0
  36. package/template/.claude/skills/security/references/threat-modeling.md +36 -0
  37. package/template/.claude/skills/ux-design/SKILL.md +113 -99
  38. package/template/.claude/skills/ux-design/{wireframe-template.md → references/wireframe-template.md} +95 -95
  39. package/template/.claude/templates/proposal.html +126 -0
  40. package/template/.claude/hooks/pre-compact.sh +0 -25
  41. package/template/.claude/hooks/session-start.sh +0 -120
  42. package/template/.claude/hooks/subagent-stop.sh +0 -11
@@ -0,0 +1,42 @@
1
+ #!/usr/bin/env node
2
+ // PreCompact hook. Fires before auto/manual compaction. Snapshots the live
3
+ // position so a post-compact session can recover. Pure Node, cross-platform.
4
+ 'use strict';
5
+ const fs = require('fs');
6
+ const path = require('path');
7
+ const { execSync } = require('child_process');
8
+
9
+ try { process.chdir(process.env.CLAUDE_PROJECT_DIR || '.'); } catch (_) {}
10
+
11
+ const sh = (cmd) => { try { return execSync(cmd, { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] }); } catch (_) { return ''; } };
12
+ const read = (f) => { try { return fs.readFileSync(f, 'utf8'); } catch (_) { return null; } };
13
+
14
+ fs.mkdirSync('docs/.snapshots', { recursive: true });
15
+ const d = new Date();
16
+ const p2 = (n) => String(n).padStart(2, '0');
17
+ const stamp = `${d.getFullYear()}${p2(d.getMonth() + 1)}${p2(d.getDate())}-${p2(d.getHours())}${p2(d.getMinutes())}${p2(d.getSeconds())}`;
18
+ const snap = path.join('docs/.snapshots', `precompact-${stamp}.md`);
19
+
20
+ const state = read('docs/STATE.md');
21
+ const body = [
22
+ `# Pre-compact snapshot ${stamp}`, '',
23
+ '## Git',
24
+ sh('git status --short').replace(/\n+$/, ''),
25
+ sh('git diff --stat').replace(/\n+$/, ''),
26
+ '',
27
+ '## STATE.md at compact time',
28
+ state !== null ? state.replace(/\n$/, '') : '(no STATE.md)',
29
+ '',
30
+ ].join('\n');
31
+ fs.writeFileSync(snap, body);
32
+
33
+ // keep the 5 newest precompact snapshots
34
+ const old = fs.readdirSync('docs/.snapshots')
35
+ .filter((f) => /^precompact-.*\.md$/.test(f))
36
+ .map((f) => ({ f, t: fs.statSync(path.join('docs/.snapshots', f)).mtimeMs }))
37
+ .sort((a, b) => b.t - a.t)
38
+ .slice(5);
39
+ for (const { f } of old) { try { fs.unlinkSync(path.join('docs/.snapshots', f)); } catch (_) {} }
40
+
41
+ process.stdout.write(`Context was compacted. Recovery snapshot saved at ${snap}.\n`);
42
+ process.stdout.write('STATE.md and ISSUES.md were re-injected by the SessionStart hook - trust those.\n');
@@ -0,0 +1,137 @@
1
+ #!/usr/bin/env node
2
+ // SessionStart hook. stdout is injected into the agent's context every session.
3
+ // Pure Node, cross-platform (macOS / Windows / Linux) — no bash/jq/python needed.
4
+ 'use strict';
5
+ const fs = require('fs');
6
+ const path = require('path');
7
+ const os = require('os');
8
+ const { execSync } = require('child_process');
9
+
10
+ try { process.chdir(process.env.CLAUDE_PROJECT_DIR || '.'); } catch (_) {}
11
+
12
+ const out = [];
13
+ const emit = (s = '') => out.push(s);
14
+ const read = (f) => { try { return fs.readFileSync(f, 'utf8'); } catch (_) { return null; } };
15
+ const exists = (f) => fs.existsSync(f);
16
+ const sh = (cmd) => { try { return execSync(cmd, { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] }); } catch (_) { return ''; } };
17
+
18
+ emit('=== iStartSoftFlow AUTO-CONTEXT (injected by hook, NOT optional) ===');
19
+ emit('');
20
+
21
+ // 1. git state
22
+ emit('## Git');
23
+ emit('branch: ' + (sh('git branch --show-current').trim() || 'n/a'));
24
+ const uncommitted = sh('git status --short').split('\n').filter((l) => l.trim() !== '').length;
25
+ emit('uncommitted: ' + uncommitted + ' file(s)');
26
+ for (const l of sh('git log --oneline -3').replace(/\n+$/, '').split('\n').filter(Boolean)) emit(' ' + l);
27
+ emit('');
28
+
29
+ // 2. active state (cap size — STATE.md is meant to stay small)
30
+ const state = read('docs/STATE.md');
31
+ if (state !== null) {
32
+ emit('## STATE.md (current position - READ THIS FIRST)');
33
+ const sl = state.replace(/\n$/, '').split('\n');
34
+ for (const l of sl.slice(0, 40)) emit(l);
35
+ if (sl.length > 40) emit(`… (+${sl.length - 40} more — STATE.md should be small; trim it)`);
36
+ emit('');
37
+ } else {
38
+ emit('## STATE.md missing -> run /overview to bootstrap the project.');
39
+ emit('');
40
+ }
41
+
42
+ // 3. issue log — inject only OPEN issues (resolved ones stay in the file for
43
+ // grep, but are NOT re-paid in tokens every session). Capped.
44
+ const issues = read('docs/ISSUES.md');
45
+ if (issues !== null) {
46
+ const blocks = [];
47
+ let cur = null;
48
+ for (const l of issues.split('\n')) {
49
+ if (/^### /.test(l)) { if (cur) blocks.push(cur); cur = [l]; }
50
+ else if (cur) cur.push(l);
51
+ }
52
+ if (cur) blocks.push(cur);
53
+ const open = blocks.filter((b) => b.some((l) => /- \[ \]/.test(l)));
54
+ emit(`## ISSUES.md (${open.length} open) - grep this file before debugging anything`);
55
+ if (open.length) {
56
+ const flat = open.flat();
57
+ for (const l of flat.slice(0, 50)) emit(l);
58
+ if (flat.length > 50) emit('… (more — grep docs/ISSUES.md for full detail)');
59
+ } else {
60
+ emit('(no open issues)');
61
+ }
62
+ emit('');
63
+ }
64
+
65
+ // 3b. research index
66
+ const idx = read('docs/research/INDEX.md');
67
+ if (idx !== null) {
68
+ const rows = idx.split('\n').filter((l) => /^[0-9]/.test(l));
69
+ emit(`## research/INDEX.md (${rows.length} prior investigations)`);
70
+ emit('grep this before any new research or debugging.');
71
+ for (const l of rows.slice(-15)) emit(' ' + l);
72
+ emit('');
73
+ }
74
+
75
+ // 3d. shared KB — pull latest + load snapshot
76
+ const KB_CONFIG = '.claude/kb-config.json';
77
+ let kbActive = false;
78
+ if (exists(KB_CONFIG)) {
79
+ let cfg = {}; try { cfg = JSON.parse(read(KB_CONFIG) || '{}'); } catch (_) {}
80
+ let kbPath = cfg.kb_path || '';
81
+ if (kbPath.startsWith('~')) kbPath = path.join(os.homedir(), kbPath.slice(1));
82
+ if (kbPath && exists(kbPath) && fs.statSync(kbPath).isDirectory()) {
83
+ emit('## Shared KB');
84
+ let pulled = true;
85
+ try { execSync(`git -C "${kbPath}" pull --ff-only --quiet`, { stdio: 'ignore' }); } catch (_) { pulled = false; }
86
+ emit(pulled ? 'KB pulled: OK' : 'KB pull skipped (offline or conflict — using local copy)');
87
+
88
+ const cut = new Date(); cut.setMonth(cut.getMonth() - 6);
89
+ const cutoff = cut.toISOString().slice(0, 10);
90
+ const today = new Date().toISOString().slice(0, 10);
91
+ const kbIndex = path.join(kbPath, 'INDEX.md');
92
+ if (exists(kbIndex)) {
93
+ const lines = (read(kbIndex) || '').split('\n').filter((l) => l && !l.startsWith('#'));
94
+ const snap = [`# KB snapshot — loaded ${today}`, `# Stale = created date older than ${cutoff}`, ''];
95
+ let stale = 0, total = 0;
96
+ for (const line of lines) {
97
+ if (line.includes('|')) total++;
98
+ const entryDate = (line.split('|')[0] || '').trim();
99
+ if (/^\d{4}-\d{2}-\d{2}$/.test(entryDate) && entryDate < cutoff) { snap.push('[STALE] ' + line); stale++; }
100
+ else snap.push(line);
101
+ }
102
+ try { fs.mkdirSync('docs', { recursive: true }); fs.writeFileSync('docs/.kb-snapshot.md', snap.join('\n') + '\n'); } catch (_) {}
103
+ emit(`KB snapshot loaded: ${total} entries (${stale} stale — researcher will re-research these)`);
104
+ emit('Snapshot at docs/.kb-snapshot.md — researcher reads this before web search.');
105
+ } else {
106
+ emit(`KB INDEX.md not found at ${kbPath} — run /store-wisdom to populate it.`);
107
+ }
108
+ emit('');
109
+ kbActive = true;
110
+ } else {
111
+ emit('## Shared KB: configured but path not found (' + (cfg.kb_path || '') + ')');
112
+ emit('Fix kb_path in .claude/kb-config.json (or re-run: npx create-issflow).');
113
+ emit('');
114
+ }
115
+ }
116
+
117
+ // 4. hard rule reminder
118
+ emit('## RULES (enforced this session)');
119
+ emit('- AUTO mode (default) governs the DEV loop: follow the plan — decide + log +');
120
+ emit(' continue, do NOT stop to ask. (Planning / grill still asks — that part is fine.)');
121
+ emit(' Hard-stops only: security / irreversible-or-outbound actions / contradictory spec.');
122
+ emit('- caveman ULTRA mode is active.');
123
+ emit('- before debugging ANY error: grep ISSUES.md AND research/INDEX.md first.');
124
+ emit('- debug attempts: WARN at 2; cap 3. AUTO: log + park the slice + continue (batched');
125
+ emit(' report at the phase boundary). GUIDED: stop and ask you.');
126
+ emit('- end of every phase: run /synthesize, then /clear.');
127
+ emit('- small obvious change? use /quick, not /phase.');
128
+ emit('- token economy: keep context lean. Delegate noisy work to subagents (they');
129
+ emit(' return summaries). If one phase grows past ~50% of the model window, split');
130
+ emit(' it or /synthesize -> /clear — do not coast to auto-compact.');
131
+ if (kbActive) {
132
+ emit('- KB active: researcher checks docs/.kb-snapshot.md before web search.');
133
+ emit('- learned something worth keeping? run /store-wisdom.');
134
+ }
135
+ emit('=== END AUTO-CONTEXT ===');
136
+
137
+ process.stdout.write(out.join('\n') + '\n');
@@ -0,0 +1,18 @@
1
+ #!/usr/bin/env node
2
+ // SubagentStop hook. Appends a trace line, keeps the log bounded. Cross-platform.
3
+ 'use strict';
4
+ const fs = require('fs');
5
+ const path = require('path');
6
+
7
+ try { process.chdir(process.env.CLAUDE_PROJECT_DIR || '.'); } catch (_) {}
8
+
9
+ fs.mkdirSync('docs/.snapshots', { recursive: true });
10
+ const log = path.join('docs/.snapshots', 'agent-trace.log');
11
+ const d = new Date();
12
+ const p2 = (n) => String(n).padStart(2, '0');
13
+ const ts = `${p2(d.getHours())}:${p2(d.getMinutes())}:${p2(d.getSeconds())}`;
14
+
15
+ let lines = [];
16
+ try { lines = fs.readFileSync(log, 'utf8').split('\n').filter(Boolean); } catch (_) {}
17
+ lines.push(`${ts} subagent finished`);
18
+ fs.writeFileSync(log, lines.slice(-50).join('\n') + '\n');