create-issflow 1.2.0 → 1.4.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/README.md +3 -3
- package/bin/cli.js +60 -5
- package/package.json +1 -1
- package/template/.claude/agents/e2e-runner.md +1 -0
- package/template/.claude/agents/planner.md +17 -2
- package/template/.claude/agents/synthesizer.md +3 -0
- package/template/.claude/agents/test-author.md +6 -2
- package/template/.claude/commands/change-request.md +4 -1
- package/template/.claude/commands/overview.md +34 -5
- package/template/.claude/commands/phase.md +17 -4
- package/template/.claude/commands/propose.md +13 -6
- package/template/.claude/commands/quick.md +1 -1
- package/template/.claude/commands/release.md +51 -0
- package/template/.claude/commands/replan.md +7 -2
- package/template/.claude/commands/runbook.md +38 -0
- package/template/.claude/commands/sprint.md +233 -0
- package/template/.claude/commands/uat.md +36 -0
- package/template/.claude/hooks/context-guard.js +101 -0
- package/template/.claude/hooks/lib/ctx.js +82 -0
- package/template/.claude/hooks/session-start.js +17 -0
- package/template/.claude/istartsoft-flow/METHODOLOGY.md +115 -21
- package/template/.claude/templates/proposal.html +87 -101
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: The Scrum sprint layer — group PLAN phases into time-/scope-boxed sprints and run the full ceremony set (planning → standups → review/demo → retrospective → close) with burndown + velocity. AUTO-facilitated: the orchestrator runs every ceremony itself and drives the sprint end-to-end without stopping, pausing only at the real hard-stops. The layer between PLAN (backlog) and PHASE (build loop).
|
|
3
|
+
argument-hint: [run|plan|standup|review|retro|close|status] [sprint number]
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
Caveman ULTRA mode. You are the ORCHESTRATOR / SCRUM MASTER. You FACILITATE the
|
|
7
|
+
ceremonies and ROUTE build work to subagents — you do NOT implement or debug yourself.
|
|
8
|
+
|
|
9
|
+
Subcommand: $ARGUMENTS (default: `status`)
|
|
10
|
+
|
|
11
|
+
Hierarchy: **PLAN (product backlog) → SPRINT (committed slice of phases) → PHASE
|
|
12
|
+
(the build loop)**. A sprint groups consecutive PLAN phases behind ONE sprint goal
|
|
13
|
+
and ships ONE deployable increment. Phases still run via `/phase` exactly as before
|
|
14
|
+
— the sprint layer wraps them with planning, standups, a review, and a retro.
|
|
15
|
+
|
|
16
|
+
**AUTONOMY (read first).** Sprint planning only SLICES the already-approved
|
|
17
|
+
`docs/PLAN.md` — the requirements gate already happened at `/overview` plan approval —
|
|
18
|
+
so the ceremonies are AUTO-safe and the orchestrator runs them WITHOUT stopping.
|
|
19
|
+
- **AUTO (default):** `/sprint run` drives the whole sprint hands-off — plan → loop
|
|
20
|
+
`/phase` → standup tick after each phase → review → retro → close → roll forward →
|
|
21
|
+
start the next sprint. Decisions (points, scope-fit, accept/carry) are made from the
|
|
22
|
+
PLAN + velocity, logged, and the run continues. Pause ONLY for a methodology
|
|
23
|
+
hard-stop (deploy/irreversible/outbound, security-sensitive change, contradictory
|
|
24
|
+
spec, or debug budget spent with no independent slice left).
|
|
25
|
+
- **GUIDED:** each ceremony presents its result and waits for you before the next.
|
|
26
|
+
The PLAN-approval, commercial (`/propose`, `/change-request`), and release
|
|
27
|
+
(`/release`, `/uat`, prod promote) gates are SEPARATE and stay interactive in both
|
|
28
|
+
modes — the sprint layer never auto-passes them.
|
|
29
|
+
|
|
30
|
+
Artifacts: `docs/sprints/sprint-<n>.md` (one per sprint — goal, committed phases,
|
|
31
|
+
burndown, standups, review, retro) and `docs/sprints/VELOCITY.md` (the rolling
|
|
32
|
+
velocity table). Create `docs/sprints/` if absent. STATE.md carries the active sprint.
|
|
33
|
+
|
|
34
|
+
=====================================================================
|
|
35
|
+
## ROUTER
|
|
36
|
+
|
|
37
|
+
- `run` → §RUN (AUTO end-to-end driver — the default way to use this)
|
|
38
|
+
- `plan` → §1 PLANNING
|
|
39
|
+
- `standup` → §2 STANDUP
|
|
40
|
+
- `review` → §3 REVIEW
|
|
41
|
+
- `retro` → §4 RETRO
|
|
42
|
+
- `close` → §5 CLOSE
|
|
43
|
+
- `status` → §STATUS (default when no subcommand)
|
|
44
|
+
|
|
45
|
+
=====================================================================
|
|
46
|
+
## §1 — SPRINT PLANNING (ceremony)
|
|
47
|
+
|
|
48
|
+
Pre: `docs/PLAN.md` exists AND its `> Approval:` header reads `approved …` (the
|
|
49
|
+
PLAN-APPROVAL gate, hard rule 13). Still `PENDING` / missing → STOP, route to the
|
|
50
|
+
`/overview` plan-approval gate. A sprint only SLICES an already-signed-off plan.
|
|
51
|
+
|
|
52
|
+
a. **Estimate (one-time, lazy).** Every pending PLAN phase needs a points estimate
|
|
53
|
+
(Fibonacci `1 2 3 5 8`, relative effort from the phase `slice` + `acceptance`
|
|
54
|
+
size; a phase that feels `>8` is too big — note it for `/replan` to split). If the
|
|
55
|
+
planner already wrote `[N pts]` tags, reuse them; otherwise assign now and write
|
|
56
|
+
them back into PLAN.md next to each phase header.
|
|
57
|
+
|
|
58
|
+
b. **Capacity.** Read `docs/sprints/VELOCITY.md`.
|
|
59
|
+
- Have history → capacity = rolling-average completed velocity (last ≤3 sprints).
|
|
60
|
+
- First sprint (no history) → capacity = `flow-config.json` `sprint.defaultCapacity`
|
|
61
|
+
(fallback 8 pts).
|
|
62
|
+
|
|
63
|
+
c. **Commit.** Walk the PLAN in dependency order, pull pending phases into this sprint
|
|
64
|
+
until the next phase would exceed capacity. Don't split a phase across a sprint
|
|
65
|
+
boundary; respect dependencies (never commit a phase whose dependency is in a later
|
|
66
|
+
sprint). Keep one coherent theme → that becomes the **sprint goal** (one line: the
|
|
67
|
+
user-visible increment this sprint ships). Optionally mark 1 phase `stretch`.
|
|
68
|
+
|
|
69
|
+
d. **Write** `docs/sprints/sprint-<n>.md` from the template below; set
|
|
70
|
+
`status: active`; seed the burndown tick 0 = total committed points. Group the
|
|
71
|
+
committed phases under a `## Sprint <n>` header in PLAN.md if not already grouped.
|
|
72
|
+
Update STATE.md: `sprint: <n> (active)`.
|
|
73
|
+
|
|
74
|
+
AUTO: auto-commit the computed backlog, log the goal + points + capacity, continue.
|
|
75
|
+
GUIDED: present goal + committed phases + points, wait for confirm.
|
|
76
|
+
|
|
77
|
+
=====================================================================
|
|
78
|
+
## §2 — STANDUP (auto-tick — fires once per phase close inside an active sprint)
|
|
79
|
+
|
|
80
|
+
The AI dev loop has no calendar days, so the "daily" standup is rebound to a
|
|
81
|
+
**per-phase-close tick** — the natural cadence of progress. After each `/phase` CLOSE
|
|
82
|
+
while a sprint is active (the `/phase` command fires this; `/sprint run` fires it
|
|
83
|
+
inline; or run `/sprint standup` by hand):
|
|
84
|
+
|
|
85
|
+
1. Append a standup line to the active sprint doc:
|
|
86
|
+
`- tick <k> (Phase <p> <done|blocked>): done <what>; next <phase/none>; blockers <none|ref>`
|
|
87
|
+
2. Update the **burndown**: append a row `tick <k> | Phase <p> <state> | <remaining pts>`
|
|
88
|
+
where remaining = committed points minus points of phases now `done`. Re-render the
|
|
89
|
+
ASCII sparkline.
|
|
90
|
+
3. Surface blockers immediately if any (a blocked phase is the standup's whole point).
|
|
91
|
+
AUTO: the blocker is already parked per the circuit breaker — just record it here
|
|
92
|
+
and keep the burndown honest. GUIDED: relay it.
|
|
93
|
+
|
|
94
|
+
Keep it ONE line per tick. No prose. The burndown is the signal.
|
|
95
|
+
|
|
96
|
+
=====================================================================
|
|
97
|
+
## §3 — SPRINT REVIEW / DEMO (ceremony — at sprint boundary)
|
|
98
|
+
|
|
99
|
+
Run when every committed phase is `done` or `blocked` (sprint timebox reached).
|
|
100
|
+
|
|
101
|
+
a. **Demo.** Summarise the shipped increment: for each accepted phase, the
|
|
102
|
+
user-visible behaviour now working (pull from the phase `slice` + `docs/ENDPOINTS.md`).
|
|
103
|
+
This is the "done = demoable" check — a phase that can't be demoed isn't done.
|
|
104
|
+
|
|
105
|
+
b. **Boundary audits.** Run the whole-product audits ONCE for the increment (cheaper
|
|
106
|
+
here than per-phase, broader than the inline gates):
|
|
107
|
+
`/ui-audit` (if UI shipped) · `/qa-audit` · `/security-audit`. Fold the scores in.
|
|
108
|
+
Open BLOCKER/HIGH/CRITICAL → route to FIX (`debugger`/`implementer`), re-audit.
|
|
109
|
+
(Security findings remain an autonomy hard-stop.)
|
|
110
|
+
|
|
111
|
+
c. **Accept / carry.** Mark each phase `accepted` (demoed + audits clean) or
|
|
112
|
+
`carried` (not done / failed audit → rolls to the next sprint at §5).
|
|
113
|
+
|
|
114
|
+
d. Write the `## Review` block into the sprint doc (demo bullets, audit scores,
|
|
115
|
+
accepted vs carried).
|
|
116
|
+
|
|
117
|
+
=====================================================================
|
|
118
|
+
## §4 — RETROSPECTIVE (ceremony — after review)
|
|
119
|
+
|
|
120
|
+
Inspect-and-adapt on the PROCESS, not the product. Write the `## Retro` block:
|
|
121
|
+
|
|
122
|
+
- **went well** — what to keep (2–4 bullets).
|
|
123
|
+
- **didn't** — friction, repeated debugging, churned tests, estimate misses.
|
|
124
|
+
- **actions** — each a CONCRETE, routed change, not a wish. Route every action to a
|
|
125
|
+
durable home so it actually happens:
|
|
126
|
+
- a recurring bug/root-cause pattern → it's already in `docs/ISSUES.md`; note the ref.
|
|
127
|
+
- a workflow/structure change → `/log-decision` (`docs/DESIGN_LOG.md`).
|
|
128
|
+
- an ops/incident lesson → `/runbook`.
|
|
129
|
+
- a plan correction (re-estimate, split, reorder) → `/replan`.
|
|
130
|
+
- a durable, cross-project lesson → flag for `/store-wisdom`.
|
|
131
|
+
- **estimate accuracy** — committed vs completed points; note phases that blew their
|
|
132
|
+
estimate so the next sprint's poker is calibrated.
|
|
133
|
+
|
|
134
|
+
AUTO: auto-apply the routed actions (log/replan) and continue. GUIDED: list actions,
|
|
135
|
+
confirm before applying.
|
|
136
|
+
|
|
137
|
+
=====================================================================
|
|
138
|
+
## §5 — SPRINT CLOSE
|
|
139
|
+
|
|
140
|
+
a. **Velocity.** completed = sum of `accepted` phase points. Append a row to
|
|
141
|
+
`docs/sprints/VELOCITY.md`:
|
|
142
|
+
`| <n> | <committed> | <completed> | <goal met? yes/no> |` and recompute the rolling
|
|
143
|
+
average (last ≤3 sprints).
|
|
144
|
+
b. **Carry forward.** Each `carried`/`blocked` phase stays pending in PLAN.md — the
|
|
145
|
+
next `/sprint plan` re-commits it first (carried work has priority). Don't lose it.
|
|
146
|
+
c. Set the sprint doc `status: done`; stamp the close date. HISTORY line:
|
|
147
|
+
`sprint <n> closed — <completed>/<committed> pts, goal <met|missed> (<date>)`.
|
|
148
|
+
d. Update STATE.md: clear the active sprint (or set the next one if `/sprint run`
|
|
149
|
+
continues).
|
|
150
|
+
|
|
151
|
+
=====================================================================
|
|
152
|
+
## §RUN — AUTO END-TO-END DRIVER (the headline: "do all the process automatically")
|
|
153
|
+
|
|
154
|
+
`/sprint run [n]` drives one full sprint — or, if you keep going, every remaining
|
|
155
|
+
sprint until the PLAN is exhausted — with NO human stop except a hard-stop:
|
|
156
|
+
|
|
157
|
+
```
|
|
158
|
+
loop while pending phases remain in PLAN.md:
|
|
159
|
+
1. §1 PLANNING → commit the next sprint from PLAN + velocity
|
|
160
|
+
2. for each committed phase, in dependency order:
|
|
161
|
+
run /phase <p> → the full build loop (its own gates + circuit breaker)
|
|
162
|
+
§2 STANDUP tick → append standup + update burndown
|
|
163
|
+
phase BLOCKED (circuit breaker parked it) → record, keep going to the next
|
|
164
|
+
INDEPENDENT phase; if none remain, end the sprint early (timebox)
|
|
165
|
+
3. §3 REVIEW → demo + boundary audits (fix blockers, re-audit)
|
|
166
|
+
4. §4 RETRO → routed actions, applied
|
|
167
|
+
5. §5 CLOSE → velocity + carry-forward + HISTORY
|
|
168
|
+
6. /synthesize → suggest /clear (token reset at the sprint boundary, like a phase)
|
|
169
|
+
AUTO: start the next sprint automatically. GUIDED: stop, report, wait.
|
|
170
|
+
HARD-STOP at any point: deploy/irreversible/outbound action, security-sensitive
|
|
171
|
+
change, contradictory spec, or debug budget spent with no independent slice left →
|
|
172
|
+
pause, surface the consolidated report, hand to the human.
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
When the last PLAN phase is `accepted`, the build is sprint-complete → recommend
|
|
176
|
+
`/release` (the pre-production pipeline) as the next step. Do NOT auto-promote to prod
|
|
177
|
+
— that is a separate, human-signed hard-stop.
|
|
178
|
+
|
|
179
|
+
=====================================================================
|
|
180
|
+
## §STATUS
|
|
181
|
+
|
|
182
|
+
Read STATE.md + the active `docs/sprints/sprint-<n>.md` + VELOCITY.md and print:
|
|
183
|
+
sprint number + goal, status, the burndown sparkline, committed vs done points,
|
|
184
|
+
the current/next phase, any open blockers, and rolling velocity. One screen. No edits.
|
|
185
|
+
|
|
186
|
+
=====================================================================
|
|
187
|
+
## SPRINT DOC TEMPLATE (`docs/sprints/sprint-<n>.md`)
|
|
188
|
+
|
|
189
|
+
```
|
|
190
|
+
# Sprint <n> — <short name>
|
|
191
|
+
goal: <one line — the user-visible increment this sprint ships>
|
|
192
|
+
status: active # planning | active | review | done
|
|
193
|
+
capacity: <cap> pts (basis: velocity avg | default)
|
|
194
|
+
|
|
195
|
+
## Committed
|
|
196
|
+
- Phase <p>: <name> [<pts> pts] [status: pending|done|blocked|accepted|carried]
|
|
197
|
+
- ...
|
|
198
|
+
## Stretch
|
|
199
|
+
- Phase <q>: <name> [<pts> pts] # optional, pulled in only if capacity frees up
|
|
200
|
+
|
|
201
|
+
## Burndown
|
|
202
|
+
tick | event | remaining pts
|
|
203
|
+
0 | sprint start | <total>
|
|
204
|
+
1 | Phase <p> done | <rem>
|
|
205
|
+
...
|
|
206
|
+
<ascii sparkline of remaining pts, e.g. 8 ▆▅▃▂ 0>
|
|
207
|
+
|
|
208
|
+
## Standups
|
|
209
|
+
- tick 1 (Phase <p> done): done <what>; next Phase <q>; blockers none
|
|
210
|
+
- ...
|
|
211
|
+
|
|
212
|
+
## Review (<date>)
|
|
213
|
+
- demo: <increment shipped — user-visible behaviour now working>
|
|
214
|
+
- audits: ui <score|n/a> · qa <score> · security <score> · code <clean|issues>
|
|
215
|
+
- accepted: <phases> carried: <phases or —>
|
|
216
|
+
|
|
217
|
+
## Retro (<date>)
|
|
218
|
+
- went well: ...
|
|
219
|
+
- didn't: ...
|
|
220
|
+
- actions: <each routed → ISSUES | DESIGN_LOG | RUNBOOK | replan | store-wisdom>
|
|
221
|
+
- estimates: committed <c> pts / completed <d> pts — <misses noted>
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
## VELOCITY TEMPLATE (`docs/sprints/VELOCITY.md`)
|
|
225
|
+
|
|
226
|
+
```
|
|
227
|
+
# Velocity
|
|
228
|
+
| sprint | committed | completed | goal met? |
|
|
229
|
+
|--------|-----------|-----------|-----------|
|
|
230
|
+
| 1 | <c> | <d> | yes/no |
|
|
231
|
+
|
|
232
|
+
rolling avg (last ≤3): <v> pts/sprint
|
|
233
|
+
```
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Manual UAT cycle — generate an all-case test-scenario document for human testers, hand it off, capture their pasted results, and drive the defect loop until every scenario passes. Used inside /release; the human-in-the-loop acceptance gate.
|
|
3
|
+
argument-hint: [optional: feature scope, or "failed" to re-issue only failures]
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
Caveman ULTRA mode. You are the ORCHESTRATOR.
|
|
7
|
+
|
|
8
|
+
UAT is a HUMAN gate: real testers run the product and report results. Your job is to
|
|
9
|
+
make that easy — produce a clear scenario sheet, capture results, and loop on defects.
|
|
10
|
+
|
|
11
|
+
## STEP 1 — BUILD SCENARIOS
|
|
12
|
+
From `docs/OVERVIEW.md` (flows) + `docs/PLAN.md` (acceptance) + `docs/ENDPOINTS.md`,
|
|
13
|
+
write `docs/UAT-<date>.md` covering ALL cases a tester should run:
|
|
14
|
+
```
|
|
15
|
+
## UAT — <project> (<date>, build <ref>)
|
|
16
|
+
| # | scenario | preconditions | steps | expected | Result (PASS/FAIL) | Notes |
|
|
17
|
+
|---|----------|---------------|-------|----------|--------------------|-------|
|
|
18
|
+
| 1 | … | … | … | … | | |
|
|
19
|
+
```
|
|
20
|
+
Cover: every happy path, every acceptance criterion, edge / negative cases, each user
|
|
21
|
+
role, and each critical flow. Group by feature, number them, leave Result + Notes
|
|
22
|
+
blank for the tester. Keep steps concrete enough to follow without you.
|
|
23
|
+
|
|
24
|
+
## STEP 2 — HAND OFF
|
|
25
|
+
Show me the scenario sheet, ready to run. Tell me to execute it (or pass it to QA /
|
|
26
|
+
the client), then **paste the results back** here. WAIT — do not assume results.
|
|
27
|
+
|
|
28
|
+
## STEP 3 — CAPTURE RESULTS
|
|
29
|
+
Take the pasted results, fill PASS/FAIL + notes into `docs/UAT-<date>.md`, and
|
|
30
|
+
summarise: **X / Y passed**, list the failures with their scenario IDs.
|
|
31
|
+
|
|
32
|
+
## STEP 4 — DEFECT LOOP
|
|
33
|
+
For each FAIL: log to `docs/ISSUES.md` (repro = the scenario steps), fix
|
|
34
|
+
(`implementer` / `debugger`, debug cap 3), re-run the automated tests for that area,
|
|
35
|
+
then re-issue **only the failed scenarios** to me for re-test. Loop until 100% PASS.
|
|
36
|
+
On all-green, hand back to `/release` for sign-off.
|
|
@@ -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 };
|
|
@@ -39,6 +39,21 @@ if (state !== null) {
|
|
|
39
39
|
emit('');
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
+
// 2b. active sprint (sprint layer) — surface goal + burndown if one is active
|
|
43
|
+
const sprintMatch = (state || '').match(/^\s*sprint:\s*(\d+)\s*\(active\)/m);
|
|
44
|
+
if (sprintMatch) {
|
|
45
|
+
const sf = read(`docs/sprints/sprint-${sprintMatch[1]}.md`);
|
|
46
|
+
if (sf !== null) {
|
|
47
|
+
emit(`## Sprint ${sprintMatch[1]} (active)`);
|
|
48
|
+
const goal = (sf.match(/goal:\s*(.+)/i) || [])[1];
|
|
49
|
+
if (goal) emit('goal: ' + goal.trim());
|
|
50
|
+
const burn = sf.split('\n').find((l) => /burndown|remaining|pts? left|[▁▂▃▄▅▆▇█]/i.test(l));
|
|
51
|
+
if (burn) emit('burndown: ' + burn.trim());
|
|
52
|
+
emit(`see docs/sprints/sprint-${sprintMatch[1]}.md for the full sprint.`);
|
|
53
|
+
emit('');
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
42
57
|
// 3. issue log — inject only OPEN issues (resolved ones stay in the file for
|
|
43
58
|
// grep, but are NOT re-paid in tokens every session). Capped.
|
|
44
59
|
const issues = read('docs/ISSUES.md');
|
|
@@ -120,6 +135,8 @@ emit('- AUTO mode (default) governs the DEV loop: follow the plan — decide + l
|
|
|
120
135
|
emit(' continue, do NOT stop to ask. (Planning / grill still asks — that part is fine.)');
|
|
121
136
|
emit(' Hard-stops only: security / irreversible-or-outbound actions / contradictory spec.');
|
|
122
137
|
emit('- caveman ULTRA mode is active.');
|
|
138
|
+
emit('- PLAN-APPROVAL gate (rule 13): no /phase or /sprint while STATE `plan:` reads');
|
|
139
|
+
emit(' PENDING — the plan needs a human sign-off via /overview first.');
|
|
123
140
|
emit('- before debugging ANY error: grep ISSUES.md AND research/INDEX.md first.');
|
|
124
141
|
emit('- debug attempts: WARN at 2; cap 3. AUTO: log + park the slice + continue (batched');
|
|
125
142
|
emit(' report at the phase boundary). GUIDED: stop and ask you.');
|