create-issflow 1.2.0 → 1.2.1
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/bin/cli.js
CHANGED
|
@@ -107,8 +107,21 @@ function copyTemplateCommands(destDir) {
|
|
|
107
107
|
// ---- adapters (keep the methodology single-source — these POINT at it) ------
|
|
108
108
|
|
|
109
109
|
function adapterClaude() {
|
|
110
|
+
// Claude Code auto-loads CLAUDE.md only — NOT AGENTS.md. Without a CLAUDE.md
|
|
111
|
+
// the methodology never enters context on its own (the SessionStart hook still
|
|
112
|
+
// injects the per-session summary, but the full single-source doc would not
|
|
113
|
+
// load). A one-line `@AGENTS.md` import gives Claude Code the full baseline
|
|
114
|
+
// while keeping ONE source of truth — no rule is restated here (anti-drift).
|
|
115
|
+
const c0 = conflicts;
|
|
116
|
+
writeFile('CLAUDE.md', claudeMd());
|
|
117
|
+
if (conflicts > c0) warnings.push('claude: you already keep a CLAUDE.md — ours was written as CLAUDE.md.issflow-new. Add a line `@AGENTS.md` to your CLAUDE.md so the methodology auto-loads.');
|
|
118
|
+
// Context-budget watchdog config (consumed by .claude/hooks/context-guard.js).
|
|
119
|
+
// Shipped default window:0 = auto-detect; flow-config.json lives at .claude root,
|
|
120
|
+
// outside build.js's copied DIRS, so the installer writes it here.
|
|
121
|
+
writeFile('.claude/flow-config.json', flowConfig());
|
|
110
122
|
const HOOKS = {
|
|
111
123
|
SessionStart: [{ matcher: 'startup|clear|compact', hooks: [{ type: 'command', command: 'node .claude/hooks/session-start.js' }] }],
|
|
124
|
+
PreToolUse: [{ matcher: '*', hooks: [{ type: 'command', command: 'node .claude/hooks/context-guard.js' }] }],
|
|
112
125
|
PreCompact: [{ matcher: 'auto|manual', hooks: [{ type: 'command', command: 'node .claude/hooks/pre-compact.js' }] }],
|
|
113
126
|
SubagentStop: [{ hooks: [{ type: 'command', command: 'node .claude/hooks/subagent-stop.js' }] }],
|
|
114
127
|
};
|
|
@@ -176,7 +189,7 @@ function adapterAider() {
|
|
|
176
189
|
const ADAPTERS = { claude: adapterClaude, codex: adapterCodex, cursor: adapterCursor, gemini: adapterGemini, aider: adapterAider };
|
|
177
190
|
|
|
178
191
|
const NEXT_STEPS = {
|
|
179
|
-
claude: 'Open Claude Code — the SessionStart hook fires automatically. Run /overview to bootstrap.',
|
|
192
|
+
claude: 'Open Claude Code — CLAUDE.md (@AGENTS.md) loads the methodology and the SessionStart hook fires automatically. Run /overview to bootstrap.',
|
|
180
193
|
codex: 'Open Codex CLI — it reads AGENTS.md. Start by running the /overview procedure (.claude/commands/overview.md).',
|
|
181
194
|
cursor: 'Open Cursor — the rule applies automatically. Run the /overview command to bootstrap.',
|
|
182
195
|
gemini: 'Open Gemini CLI — it reads GEMINI.md. Run the SESSION-OPEN ritual, then the overview procedure.',
|
|
@@ -199,7 +212,7 @@ function agentsMd() {
|
|
|
199
212
|
'planner · researcher · implementer · test-author · debugger · e2e-runner · synthesizer', '',
|
|
200
213
|
'## Procedures — `.claude/commands/` (run as `/name`)', '',
|
|
201
214
|
'/overview · /propose · /phase · /ui-audit · /qa-audit · /security-audit ·',
|
|
202
|
-
'/change-request · /replan · /quick · /synthesize · /store-wisdom · /log-issue ·',
|
|
215
|
+
'/change-request · /replan · /quick · /synthesize · /runbook · /store-wisdom · /log-issue ·',
|
|
203
216
|
'/log-decision · /unstuck', '',
|
|
204
217
|
'## Skills — `.claude/skills/` (loaded on demand)', '',
|
|
205
218
|
'caveman · grill-me · karpathy-guidelines · ux-design · security (Secure SDLC) · code-standards', '',
|
|
@@ -225,6 +238,40 @@ function agentsMd() {
|
|
|
225
238
|
].join('\n');
|
|
226
239
|
}
|
|
227
240
|
|
|
241
|
+
function claudeMd() {
|
|
242
|
+
return [
|
|
243
|
+
'# CLAUDE.md — iStartSoftFlow (Claude Code entry)', '',
|
|
244
|
+
'@AGENTS.md', '',
|
|
245
|
+
'The import above is the single source of truth — it points to',
|
|
246
|
+
'`.claude/istartsoft-flow/METHODOLOGY.md` (read on demand). Do NOT restate any',
|
|
247
|
+
'rule here; this file only wires Claude-native mechanisms (anti-drift invariant).', '',
|
|
248
|
+
'## Claude-native wiring (automatic — see `.claude/settings.json`)', '',
|
|
249
|
+
'- **SessionStart** hook injects git state + `docs/STATE.md` + open `docs/ISSUES.md`',
|
|
250
|
+
' + the rule summary each session — read those first.',
|
|
251
|
+
'- **PreCompact** + **SubagentStop** hooks run their rituals automatically.',
|
|
252
|
+
'- Commands in `.claude/commands/` run as `/name`; agents in `.claude/agents/` are',
|
|
253
|
+
' native subagents.', '',
|
|
254
|
+
].join('\n');
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
function flowConfig() {
|
|
258
|
+
return JSON.stringify({
|
|
259
|
+
context: {
|
|
260
|
+
window: 0,
|
|
261
|
+
warnPct: 60,
|
|
262
|
+
gatePct: 78,
|
|
263
|
+
_note: 'Context-budget watchdog (.claude/hooks/context-guard.js, PreToolUse hook). '
|
|
264
|
+
+ 'window 0 = auto-detect from the model id (200000 for standard models). '
|
|
265
|
+
+ 'Some 1M-context models report their id WITHOUT a [1m] tag, so auto-detect '
|
|
266
|
+
+ 'assumes 200000 and may false-gate around 156k — if you run a 1M model, set '
|
|
267
|
+
+ 'window:1000000 here by hand. warnPct = soft non-blocking nudge; gatePct = hard '
|
|
268
|
+
+ 'block on NEW build work (Edit/Write-to-source/feature Task). Checkpoint paths '
|
|
269
|
+
+ '(docs/**, STATE/ISSUES/snapshots), the synthesizer subagent, and all Bash are '
|
|
270
|
+
+ 'never blocked.',
|
|
271
|
+
},
|
|
272
|
+
}, null, 2) + '\n';
|
|
273
|
+
}
|
|
274
|
+
|
|
228
275
|
// ---- main -------------------------------------------------------------------
|
|
229
276
|
|
|
230
277
|
function main() {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-issflow",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.1",
|
|
4
4
|
"description": "Scaffold the iStartSoftFlow AI-coding workflow into a project. Stack-agnostic, tool-agnostic (Claude Code, Codex, Cursor, Gemini, Aider), non-destructive.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"create-issflow": "bin/cli.js"
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Capture an operational/incident scenario in docs/RUNBOOK.md — one grep-able markdown so prod-debug knowledge isn't re-derived under pressure.
|
|
3
|
+
argument-hint: [scenario, or "from the incident we just solved"]
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
Caveman ULTRA mode.
|
|
7
|
+
|
|
8
|
+
Distill an operational scenario into docs/RUNBOOK.md. Scenario: $ARGUMENTS
|
|
9
|
+
|
|
10
|
+
This is the runbook pattern Anthropic's Security + Data-Infra teams converged on
|
|
11
|
+
independently: consolidate stack traces, dashboard signals, and the exact
|
|
12
|
+
remediation commands into ONE searchable file so the next incident is a lookup,
|
|
13
|
+
not a re-investigation.
|
|
14
|
+
|
|
15
|
+
If $ARGUMENTS says "from the incident we just solved" (or similar), distill the
|
|
16
|
+
debugging session in context — symptom, the REAL root cause, the commands that
|
|
17
|
+
fixed it — instead of asking the user to re-type it.
|
|
18
|
+
|
|
19
|
+
Append an entry to docs/RUNBOOK.md (create the file if missing). Canonical format:
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
### <scenario title — searchable, literal symptom keywords>
|
|
23
|
+
|
|
24
|
+
- trigger: <what you observe first — alert, error string, dashboard signal>
|
|
25
|
+
- diagnose: <steps/commands to confirm the real root cause>
|
|
26
|
+
- remediate: <the exact commands/actions that fix it>
|
|
27
|
+
- verify: <how to confirm recovery — what "healthy" looks like>
|
|
28
|
+
- refs: <dashboard URLs, docs, related docs/ISSUES.md titles>
|
|
29
|
+
|
|
30
|
+
```
|
|
31
|
+
Rules:
|
|
32
|
+
- title carries literal symptom keywords -> grep finds it mid-incident.
|
|
33
|
+
- remediate must be runnable as-is; no "investigate further".
|
|
34
|
+
- mark any step that is irreversible / PROD-writing with ⚠ so it's never run blind.
|
|
35
|
+
- cross-link the matching docs/ISSUES.md entry instead of duplicating root-cause prose.
|
|
36
|
+
- newest scenario at the TOP.
|
|
37
|
+
|
|
38
|
+
Confirm the entry back in 2 lines.
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
// PreToolUse context watchdog (iStartSoftFlow). Two tiers, one hook:
|
|
4
|
+
// warnPct -> non-blocking nudge (additionalContext) once per climb into the band
|
|
5
|
+
// gatePct -> HARD block of NEW build work (Edit/Write-to-source/feature Task)
|
|
6
|
+
// Reads REAL token usage from the transcript. Fail-OPEN: any error -> allow,
|
|
7
|
+
// never wedge the tool loop on a hook bug.
|
|
8
|
+
const path = require('path');
|
|
9
|
+
const fs = require('fs');
|
|
10
|
+
|
|
11
|
+
const silent = () => process.exit(0);
|
|
12
|
+
const out = (obj) => { process.stdout.write(JSON.stringify(obj)); process.exit(0); };
|
|
13
|
+
|
|
14
|
+
let input = '';
|
|
15
|
+
process.stdin.setEncoding('utf8');
|
|
16
|
+
process.stdin.on('data', (d) => (input += d));
|
|
17
|
+
process.stdin.on('end', () => {
|
|
18
|
+
let evt;
|
|
19
|
+
try { evt = JSON.parse(input); } catch (_) { return silent(); }
|
|
20
|
+
try { run(evt); } catch (_) { silent(); }
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
function run(evt) {
|
|
24
|
+
const projectDir = process.env.CLAUDE_PROJECT_DIR || evt.cwd || '.';
|
|
25
|
+
let ctx;
|
|
26
|
+
try { ctx = require(path.join(projectDir, '.claude/hooks/lib/ctx.js')); } catch (_) { return silent(); }
|
|
27
|
+
const cfg = ctx.loadConfig(projectDir);
|
|
28
|
+
const warn = cfg.warnPct || 60;
|
|
29
|
+
const gate = cfg.gatePct || 78;
|
|
30
|
+
|
|
31
|
+
const u = ctx.contextUsage(evt.transcript_path, cfg);
|
|
32
|
+
if (!u) return silent();
|
|
33
|
+
|
|
34
|
+
const tool = evt.tool_name || '';
|
|
35
|
+
const ti = evt.tool_input || {};
|
|
36
|
+
const band = u.pct >= gate ? 'gate' : u.pct >= warn ? 'warn' : 'ok';
|
|
37
|
+
const BLOCKABLE = new Set(['Edit', 'Write', 'MultiEdit', 'NotebookEdit', 'Task']);
|
|
38
|
+
|
|
39
|
+
// HARD GATE — block new build mutations; reason is fed to the model.
|
|
40
|
+
if (band === 'gate' && BLOCKABLE.has(tool) && !isEscape(tool, ti)) {
|
|
41
|
+
return out({
|
|
42
|
+
hookSpecificOutput: {
|
|
43
|
+
hookEventName: 'PreToolUse',
|
|
44
|
+
permissionDecision: 'deny',
|
|
45
|
+
permissionDecisionReason: gateReason(u, gate),
|
|
46
|
+
},
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// NON-BLOCKING WARN — emit once each time we climb into a higher band.
|
|
51
|
+
const bandFile = path.join(projectDir, 'docs/.snapshots/.ctx-band');
|
|
52
|
+
const rank = (b) => (b === 'gate' ? 2 : b === 'warn' ? 1 : 0);
|
|
53
|
+
let prev = 'ok';
|
|
54
|
+
try { prev = (fs.readFileSync(bandFile, 'utf8').trim() || 'ok'); } catch (_) {}
|
|
55
|
+
|
|
56
|
+
if (rank(band) !== rank(prev)) {
|
|
57
|
+
try { fs.mkdirSync(path.dirname(bandFile), { recursive: true }); fs.writeFileSync(bandFile, band); } catch (_) {}
|
|
58
|
+
}
|
|
59
|
+
if (rank(band) > rank(prev) && band !== 'ok') {
|
|
60
|
+
return out({
|
|
61
|
+
hookSpecificOutput: {
|
|
62
|
+
hookEventName: 'PreToolUse',
|
|
63
|
+
additionalContext: band === 'gate' ? gateReason(u, gate) : warnReason(u, warn, gate),
|
|
64
|
+
},
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
return silent();
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Checkpoint/logging writes + the synthesizer ritual are never blocked, so the
|
|
71
|
+
// model always has an escape path out of the gate.
|
|
72
|
+
function isEscape(tool, ti) {
|
|
73
|
+
if (tool === 'Edit' || tool === 'Write' || tool === 'MultiEdit' || tool === 'NotebookEdit') {
|
|
74
|
+
const fp = ti.file_path || ti.path || ti.notebook_path || '';
|
|
75
|
+
return /(^|\/)docs\//.test(fp) || /STATE\.md|ISSUES\.md|\.snapshots\//.test(fp);
|
|
76
|
+
}
|
|
77
|
+
if (tool === 'Task') return (ti.subagent_type || '').toLowerCase() === 'synthesizer';
|
|
78
|
+
return false;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const fmt = (n) => (n >= 1000 ? Math.round(n / 1000) + 'k' : String(n));
|
|
82
|
+
|
|
83
|
+
function gateReason(u, gate) {
|
|
84
|
+
return [
|
|
85
|
+
`⛔ CONTEXT GATE — ${u.pct}% (${fmt(u.tokens)}/${fmt(u.window)} tok), เกิน ${gate}% = หยุดเปิดงาน build ใหม่.`,
|
|
86
|
+
'ทำก่อนไปต่อ:',
|
|
87
|
+
' 1) ปิด/commit งานค้างให้จบ (Bash/git ไม่ถูก block)',
|
|
88
|
+
' 2) /synthesize (อัด handoff docs — subagent นี้ไม่ถูก block)',
|
|
89
|
+
' 3) /clear (session ใหม่ บางลง)',
|
|
90
|
+
'build ต่อหลัง clear. กลาง irreversible op? ใช้ Bash ปิดให้จบก่อน clear.',
|
|
91
|
+
'ปลดล็อกชั่วคราว: เพิ่ม gatePct ใน .claude/flow-config.json.',
|
|
92
|
+
].join('\n');
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function warnReason(u, warn, gate) {
|
|
96
|
+
return [
|
|
97
|
+
`⚠️ CONTEXT ${u.pct}% (${fmt(u.tokens)}/${fmt(u.window)} tok) — แตะ warn band ${warn}%.`,
|
|
98
|
+
`วางแผนปิด phase: ทยอย /synthesize → /clear ก่อนถึง gate ${gate}% (เลยจุดนั้น hook block งาน build ใหม่).`,
|
|
99
|
+
'Delegate งาน noisy ให้ subagent เพื่อกัน context โต.',
|
|
100
|
+
].join('\n');
|
|
101
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
// Shared context-budget math for iStartSoftFlow watchdog hooks.
|
|
3
|
+
// Reads the live Claude Code transcript (JSONL) and reports how full the
|
|
4
|
+
// model's context window currently is — from the REAL token usage the API
|
|
5
|
+
// reported, not a heuristic. Pure Node, cross-platform.
|
|
6
|
+
const fs = require('fs');
|
|
7
|
+
|
|
8
|
+
// Known context windows by model-id substring. First match wins. The 1M
|
|
9
|
+
// Opus/Sonnet variants advertise "[1m]" in the model id.
|
|
10
|
+
const WINDOWS = [
|
|
11
|
+
[/\[1m\]|-1m\b|:1m\b|1m-/i, 1000000],
|
|
12
|
+
[/opus|sonnet|haiku|claude/i, 200000],
|
|
13
|
+
];
|
|
14
|
+
|
|
15
|
+
function inferWindow(model) {
|
|
16
|
+
if (!model) return 200000;
|
|
17
|
+
for (const [re, w] of WINDOWS) if (re.test(model)) return w;
|
|
18
|
+
return 200000;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// Read project flow config; returns the `context` block or {}.
|
|
22
|
+
function loadConfig(projectDir) {
|
|
23
|
+
try {
|
|
24
|
+
const cfg = JSON.parse(fs.readFileSync(projectDir + '/.claude/flow-config.json', 'utf8'));
|
|
25
|
+
return (cfg && cfg.context) || {};
|
|
26
|
+
} catch (_) { return {}; }
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Read only the last `bytes` of a file (the recent assistant turns live at the
|
|
30
|
+
// tail of the JSONL — no need to load a multi-MB transcript on every tool).
|
|
31
|
+
function readTail(p, bytes) {
|
|
32
|
+
const fd = fs.openSync(p, 'r');
|
|
33
|
+
try {
|
|
34
|
+
const size = fs.fstatSync(fd).size;
|
|
35
|
+
const start = Math.max(0, size - bytes);
|
|
36
|
+
const len = size - start;
|
|
37
|
+
const buf = Buffer.alloc(len);
|
|
38
|
+
fs.readSync(fd, buf, 0, len, start);
|
|
39
|
+
return { text: buf.toString('utf8'), partial: start > 0 };
|
|
40
|
+
} finally { fs.closeSync(fd); }
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Scan lines (newest first) for the most recent assistant usage block.
|
|
44
|
+
// input_tokens + cache_read + cache_creation == the full prompt size actually
|
|
45
|
+
// sent == current context occupancy.
|
|
46
|
+
function scanUsage(text, dropFirst) {
|
|
47
|
+
const lines = text.split('\n');
|
|
48
|
+
const lo = dropFirst ? 1 : 0; // first line may be a truncated tail fragment
|
|
49
|
+
for (let i = lines.length - 1; i >= lo; i--) {
|
|
50
|
+
const ln = lines[i].trim();
|
|
51
|
+
if (!ln) continue;
|
|
52
|
+
let obj;
|
|
53
|
+
try { obj = JSON.parse(ln); } catch (_) { continue; }
|
|
54
|
+
const m = obj && obj.message;
|
|
55
|
+
if (m && m.role === 'assistant' && m.usage && typeof m.usage.input_tokens === 'number') {
|
|
56
|
+
return { usage: m.usage, model: m.model || obj.model || null };
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function contextUsage(transcriptPath, cfg) {
|
|
63
|
+
if (!transcriptPath) return null;
|
|
64
|
+
let hit;
|
|
65
|
+
try {
|
|
66
|
+
const tail = readTail(transcriptPath, 512 * 1024);
|
|
67
|
+
hit = scanUsage(tail.text, tail.partial);
|
|
68
|
+
if (!hit && tail.partial) {
|
|
69
|
+
// usage not in the tail window — fall back to a full read (rare).
|
|
70
|
+
hit = scanUsage(fs.readFileSync(transcriptPath, 'utf8'), false);
|
|
71
|
+
}
|
|
72
|
+
} catch (_) { return null; }
|
|
73
|
+
if (!hit) return null;
|
|
74
|
+
const u = hit.usage;
|
|
75
|
+
const tokens = (u.input_tokens || 0)
|
|
76
|
+
+ (u.cache_read_input_tokens || 0)
|
|
77
|
+
+ (u.cache_creation_input_tokens || 0);
|
|
78
|
+
const window = (cfg && cfg.window) ? cfg.window : inferWindow(hit.model);
|
|
79
|
+
return { tokens, window, model: hit.model, pct: Math.round((tokens / window) * 100) };
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
module.exports = { contextUsage, inferWindow, loadConfig, readTail, scanUsage };
|
|
@@ -421,7 +421,7 @@ the same everywhere — only the *wiring* differs.
|
|
|
421
421
|
|
|
422
422
|
| Host | Entry file | Commands | Subagents | Lifecycle hooks | Shared KB |
|
|
423
423
|
|------|-----------|----------|-----------|-----------------|-----------|
|
|
424
|
-
| **Claude Code** (reference) | `AGENTS.md` + `.claude/` | `.claude/commands/` | native | SessionStart · PreCompact · SubagentStop
|
|
424
|
+
| **Claude Code** (reference) | `CLAUDE.md` (`@AGENTS.md`) + `.claude/` | `.claude/commands/` | native | SessionStart · PreToolUse (context-budget watchdog) · PreCompact · SubagentStop | yes |
|
|
425
425
|
| **Codex CLI** | `AGENTS.md` (native) | `.claude/commands/` (read as prompts) | read as reference | model-run | yes |
|
|
426
426
|
| **Cursor** | `.cursor/rules/` + `AGENTS.md` | `.cursor/commands/` | reads `.claude/agents/` | `.cursor/hooks.json` (sessionStart · subagentStop) | yes |
|
|
427
427
|
| **Gemini CLI** | `GEMINI.md` + `AGENTS.md` | `.claude/commands/` (read as prompts) | read as reference | model-run | yes |
|